Skip to content
On This Page

Features and runtime behavior

Doctrine uses one route model and React shell for development SSR, production prerendering, and browser hydration. The commands differ in lifecycle, not in the page model they render.

MDX first

Development server

doctrine dev creates a custom Vite server. For each document request it loads the current server entry, finds the normalized route, imports the MDX module, renders HTML, and lets Vite transform the result before responding.

The watcher handles two related sources:

  • MDX add, change, and unlink events update @amamo/mdx records.
  • meta.ts and meta.<locale>.ts changes invalidate navigation and icon virtual modules and trigger a full reload.

Serve mode tolerates transient navigation mismatches while related files are saved. A newly added page does not enter the route list until its nearest navigation file references it; production is strict.

Static site generation

doctrine build runs the complete pipeline:

  1. Validate output safety, config, locales, and navigation.
  2. Build browser and SSR environments with Vite.
  3. Import the SSR entry and enumerate document routes.
  4. Prerender each route plus a shared 404.html.
  5. Emit route-level authored MDX sources when page actions are enabled.
  6. Remove Vite's private manifest.
  7. Build a Pagefind index from the final output directory.

Every document page includes its <html lang>, title, description, canonical URL, matching locale alternates, and shared assets. When a default-locale translation exists, Doctrine also emits an x-default alternate.

Routes and navigation

The content path becomes the route slug:

text
docs/index.mdx                  -> /
docs/guide/page.mdx             -> /guide/
docs/reference/config.mdx       -> /reference/config/
docs/reference/config.zh-CN.mdx -> /zh-CN/reference/config/

Both index.mdx and page.mdx mean the containing directory, so do not use both for the same locale in one directory. Other basenames become a trailing-slash route.

Navigation comes only from colocated metadata. Each item names either a direct page or a direct child directory; arrays define exact order, page titles, group titles, and optional icons. A production build rejects missing, duplicate, and nonexistent references.

A page entry may resolve to a same-directory .tsx file instead of MDX. Doctrine imports only TSX files named by the nearest locale navigation file, so unlisted files remain ordinary components and are not parsed as pages. A TSX page uses its navigation title and route, but renders only inside the shared header and footer without the documentation sidebar, prose styles, or table of contents. An MDX file and TSX file cannot share the same page basename and locale.

TypeScript
export default {
  items: [{ page: 'landing', title: 'Landing page' }],
}
TSX
export default function LandingPage() {
  return <main>Build any React layout here.</main>
}

Doctrine's default MDX link component adds the deployment base to root-relative links. External https://... links open in a new tab. Registering a custom a component replaces this behavior.

Page actions and authored Markdown

By default, every MDX document page has a Copy Page action. Its menu also provides View as Markdown. Open in GitHub appears when githubUrl is configured and the repository-relative source root can be inferred or is set with githubSourceRoot. The GitHub action targets the actual source file for the current locale through blob/HEAD, which lets GitHub resolve the repository's default branch. A translated route therefore opens its suffixed source file rather than the default-locale document.

Doctrine serves the same authored source at the route-level .md URL during development and writes it during production builds:

text
/                         -> /index.md
/index/                   -> /index/index.md
/guide/install/           -> /guide/install.md
/zh-CN/guide/install/     -> /zh-CN/guide/install.md

The extra index segment keeps the root and an actual /index/ document from claiming the same file.

This is the original MDX source, not Markdown reconstructed from the rendered HTML. It can include frontmatter, ESM imports, JavaScript expressions, and JSX. Copy Page copies that same source, while View as Markdown opens its .md URL.

Standalone TSX pages do not show these actions and do not get .md source routes. Set pageActions: false to disable the actions and MDX source output for the whole site. This setting does not remove the separate GitHub link from the header.

Production search indexes rendered HTML, including text emitted by custom MDX components. The browser loads Pagefind after the user enters a query, waits briefly for more input, and shows at most eight results. ⌘K and Ctrl+K open the dialog.

Development does not write a permanent index. The first search request builds Pagefind files in memory from current SSR output. Content or navigation changes invalidate that cache; the next search request rebuilds it.

Localization

The default locale has no route prefix. Other configured locales use an encoded prefix, and the language control appears only when another document has the same slug.

Navigation and page metadata never fall back between locale files. Locale switch labels fall back to the locale code.

Doctrine's built-in interface labels are Chinese for locale names beginning with zh and English for every other locale. Content, navigation, site metadata, route prefixes, and <html lang> still use the exact configured locale.

Themes and customization

The theme control stores light or dark in localStorage. An inline head script applies the saved choice—or the operating-system preference on first visit—before the document body renders.

Doctrine ships precompiled interface CSS and built-in Badge, Callout, Card, CardGrid, CodeBlock, FileTree, FileTreeFolder, FileTreeFile, InstallTabs, LivePreview, Step, Steps, Tab, and Tabs components. Semantic details, definitions, figures, keyboard input, highlights, quotes, and tables are styled without wrappers. A configured CSS file loads after the defaults. A custom component map is merged after built-ins and is shared by SSR and browser hydration.

See MDX components for the built-in reference and examples.

Tailwind is optional. Doctrine enables its Vite plugin only when the documentation project's root package.json declares tailwindcss in dependencies, dev dependencies, or optional dependencies.

Deployment subpaths

The pathname of siteUrl becomes Vite's public base. Doctrine applies it to assets, default MDX links, locale routes, Pagefind results, canonical URLs, and rendered 404 assets without nesting the output directory.

Trust boundary

Static output does not make source code inert. MDX may contain ESM and JavaScript expressions; referenced TSX pages, doctrine.config.*, navigation modules, and custom components are executable modules. They can run during config loading, bundling, SSR, or hydration. Build only sources you trust with the privileges of the build machine and deployed site.

The built-in frontmatter schema validates only an optional string description; it is not a sandbox. See the @amamo/mdx security model for the compiler's detailed boundaries.

copyright © 2026 白熱。