Skip to content
duxt

Sources

One list of sources becomes every collection, prefix and version the site serves.

A source is a folder of Markdown: in this repository, or in another one, at the current checkout or at named refs. The sources list is the only place they are declared, and everything else is generated from it.

sources: [
  { path: 'docs', slug: 'acme' },
  { repo: 'acme/api', path: 'docs', refs: ['main', 'v2.0.0', 'v1.4.0'] }
]

Two repositories and three refs: five collections, the URL prefixes that serve them, a version switcher, the redirect rules and the sitemap entries. None of it is written by hand. Every field is in the sources reference.

How it works

Content v3 already does the hard half. A collection source takes a repository URL, a branch or tag and credentials, and Content downloads it and caches it by hash — multi-repo, private repositories and reading from a tag all exist there. duxt does not reimplement any of it.

What duxt adds happens at config load time. content.config.ts is executed code, not a data file, so it can read the site's own app.config.ts and compute its collections from that list — one collection per repository × ref, each with an absolute cwd, a name derived from the slug, and the page schema the theme needs. The same list is resolved a second time by the build into the manifest: which collection serves which URL prefix, which ref is the default, where each page came from. That manifest is what the theme reads, and it is written back into the app config as resolvedSources.

Why it's built this way

Declaring the collections by hand does not scale in the one direction documentation actually grows: three versions across fourteen repositories is forty-two declarations, and each release edits all fourteen. The shorthand keeps the list the length of the number of projects.

The cost is that a source list expresses less than a hand-written collection can. That is a deliberate trade: a consumer who needs something the shorthand cannot say writes an own content.config.ts and takes over completely, because Content merges the file from every layer with the later one winning.

What falls out of it

Because a source names a repository, a ref and a folder, several features need no configuration of their own: "Edit this page", the last-updated date and the contributor list are all derived from it. Reading a remote source's history costs a full clone, so it waits for history: true — a shallow clone would report the tip's author for every file, which is wrong data rather than missing data.

Was this page helpful?