Skip to content
duxt

Databases

Which Content database adapters a duxt site can deploy on, which of them have been verified, and the one rule that keeps the layer out of the way.

Content v3 keeps its pages in SQL and can talk to five databases. duxt has an opinion about none of them: the adapter is your deployment's decision, Content already owns the surface, and the layer's only job is not to get in the way.

This page says how far that has actually been checked, rather than implying it.

Two databases, not one

Almost every surprise in this area comes from reading "the database" as one thing. Content keeps two, and they have different lifetimes and different types.

content._localDatabasecontent.database
What it holdsThe parse cache — one row per file, as parsedThe collections the site queries
When it existsDuring a build, in .data/content/On the deployed server
Which adapterssqlite or d1, and nothing elseAny of the five below
Who reads itduxt report, the build validator, the redirect mapqueryCollection(), on a request

The consequence is the useful part: choosing an adapter changes the deployed database and nothing else. A site deployed on PostgreSQL still builds through a local SQLite parse cache, because Content's own type for that cache admits nothing else.

The adapters

content.database.type in your own nuxt.config.ts. duxt sets it for no site; leaving it unset gives you Content's default.

AdapterNeeds installedduxt
sqlite (default)Nothing on Node 24 — read through the built-in node:sqliteVerified in CI on every commit
d1Nothing; a Cloudflare bindingVerified — this site runs on it
libsql@libsql/clientVerified in CI
postgresqlpgVerified in CI
pglite@electric-sql/pgliteUntested

"Verified in CI" means a build of this repository's own site against that adapter, followed by a probe of every endpoint that reads the database at runtime and a run of duxt report. The workflow is .github/workflows/adapters.yml; it runs on each pull request, so the rows above cannot quietly stop being true.

No native SQLite driver is installed

Content's own default is better-sqlite3, a native addon that compiles through node-gyp. duxt sets content.experimental.sqliteConnector to 'native', which uses Node 24's built-in node:sqlite instead and needs no package at all — which is why the layer's install pulls no compiler and why the build wants Node 24.

You inherit that setting by extending the layer, and you should never see a deprecation warning for it. The option Content deprecated is the older experimental.nativeSqlite; duxt has a test that reads Content's own declarations and fails the layer's checks if anything it sets under content.experimental carries an @deprecated tag, so the next rename lands in this repository rather than in your build log.

What runs during the build, and never on the server

Three parts of the layer read the filesystem outright. All three run inside a build, which is what makes a Worker — or any target with no disk — possible at all.

What it doesWhen
content-cache.tsOpens the parse cache with node:sqlite, and puts it into WAL modeBuild
modules/git-meta.tsRuns git log over the checkout for "last updated" and contributorsBuild
duxt reportReads the same parse cache from the command lineBeside the build

None of them is reachable from a request, and none of them is affected by your adapter: they read the local SQLite cache described above, which exists whatever content.database says.

The rule

The layer never reads the content database at runtime through anything but queryCollection().

That is the whole of duxt's adapter compatibility. The moment a server route opened the database itself, the layer would start assuming a filesystem and a SQLite dialect — and it would fail in your deploy rather than in this repository's tests.

It is enforced rather than promised: tests/content-database.test.ts fails if any file under server/ or app/ imports a database driver, and keeps the list of build-time files that may open one closed, so a new one has to be justified in writing before it lands.

What reads the database on a request

Everything else a duxt site serves is a file by the time it is served. These are not, and these are what the adapter matrix probes:

  • …/page.md — the Markdown source behind "View as Markdown" and the hand-off links.
  • /mcp — every tool, since each answers out of a collection.
  • /llms.txt and /llms-full.txt — both walk every collection.
  • /rss.xml, once you configure it.

A page is on that list too on a server-rendered deployment, and off it on a prerendered one. See Deploy a site for which of those you are running.

Was this page helpful?