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._localDatabase | content.database | |
|---|---|---|
| What it holds | The parse cache — one row per file, as parsed | The collections the site queries |
| When it exists | During a build, in .data/content/ | On the deployed server |
| Which adapters | sqlite or d1, and nothing else | Any of the five below |
| Who reads it | duxt report, the build validator, the redirect map | queryCollection(), 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.
.data/content/contents.sqlite, in the directory you build in. It is a cache —
deleting it costs one build, never any content.
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.
| Adapter | Needs installed | duxt |
|---|---|---|
sqlite (default) | Nothing on Node 24 — read through the built-in node:sqlite | Verified in CI on every commit |
d1 | Nothing; a Cloudflare binding | Verified — this site runs on it |
libsql | @libsql/client | Verified in CI |
postgresql | pg | Verified in CI |
pglite | @electric-sql/pglite | Untested |
"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.
Nothing is known to be wrong with it. Nothing is known to be right with it either — it has had no dedicated run, so it is marked untested rather than implied to work. If you deploy on it, the layer would like to hear how it went.
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 does | When | |
|---|---|---|
content-cache.ts | Opens the parse cache with node:sqlite, and puts it into WAL mode | Build |
modules/git-meta.ts | Runs git log over the checkout for "last updated" and contributors | Build |
duxt report | Reads the same parse cache from the command line | Beside 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.txtand/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.