---
description: Understand Doctrine's development server, static build, navigation, search, localization, and trust boundaries.
---

# 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.

<Badge>MDX first</Badge>

## 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.

```ts
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.

## Search

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. <kbd>⌘K</kbd> and <kbd>Ctrl+K</kbd> 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](/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](https://jikkai.github.io/mdx/security/) for the compiler's
detailed boundaries.
