.md and .mdx files in src/routes are pages. They use the same layouts, middleware, loaders, actions, and SPA navigation as .tsx pages. The only difference is the page body is written in markdown.
about.tsx and about.mdx exist.
Frontmatter and toc
YAML frontmatter and a heading table of contents are passed as props, together with any loader data.
src/routes/guide.mdx
- Flattened frontmatter fields (
title,description) sit next to loader props. Loader fields win on conflict. frontmatteris the full object.tocis a nested list of headings. Heading ids match the slugs rendered on the page (hello-worldfor## Hello world). Use static heading text so the slug stays readable —# {props.title}becomespropstitlebecause the TOC is built from source.
src/routes/_layout.tsx
useLoaderData() also includes frontmatter and toc on markdown routes.
Layouts, middleware, loaders
Nothing else changes.src/routes/guide.server.tsx
src/routes/_layout.tsx, _layout.server.tsx, _middleware.ts, and src/middleware wrap markdown pages the same way they wrap JSX pages. Island files work too: notes.island.mdx.
MDX
.mdx can use JSX. Components from src/components/markdown are already in scope — do not import them. .md is markdown only (GFM tables, task lists, and strikethrough are on by default).
src/routes/docs/counter.mdx
Counter is the default export of src/components/markdown/Counter.tsx.
Markdown components
Default exports insrc/components/markdown are registered automatically.
src/components/markdown/Callout.tsx
markdown.componentsDir:
vite.config.ts
Plugins
Pass remark / rehype / recma plugins.remark-gfm and rehype-slug are already included.
vite.config.ts
Custom renderer (comark)
The default compiler is@mdx-js/mdx. Replace it when you want another engine.
parse is the HTML path: return markup and we wrap it as a React page. Frontmatter and toc still become props.
vite.config.ts
file is { id, filename, source, content, frontmatter, toc, format }. content is the body with YAML already stripped.
compile replaces the whole module. Return JavaScript whose default export is a React component. Missing frontmatter / toc exports are filled in for you.
compile wins if both are set.
TypeScript
*.md / *.mdx default exports are typed as React pages with frontmatter and toc named exports. Add better-router/client to tsconfig compilerOptions.types or include client.d.ts as you already do for the other virtual modules.