Build a Docs Site

Use the source primitive

Use the source primitive

createDocsSource() is the framework-neutral way to render leadtype-authored MDX in your own docs app. This page shows the wiring on top of the most common hosts.

If you're using Fumadocs specifically, use leadtype/fumadocs — it's a thinner wrapper around this same primitive.

TL;DR

The primitive itself is identical across frameworks:

lib/source.ts

Wire createMdxSourcePlugins() into your bundler's remark stack, then call source.loadPage(slug) from your framework's page renderer. The "Wire into your framework" section below has minimal setups for each host.

Install

Plus an MDX integration for your bundler (@next/mdx, @astrojs/mdx, @mdx-js/rollup, etc.).

Wire into your framework

createMdxSourcePlugins() expands <include> partials and resolves <ExtractedTypeTable> at build time, while leaving every custom tag (<Callout>, <Tabs>, <Steps>, …) as JSX for your runtime components. Set typeTableBasePath to the source root that contains referenced TypeScript files; use path.resolve(process.cwd(), "docs") only when those files intentionally live under your docs folder.

Next App Router

next.config.mjs
app/docs/[[...slug]]/page.tsx

Astro Content Collections

astro.config.mjs

Use Astro's native content collection schema for typed frontmatter. Call source.loadPage() from leadtype only when you need programmatic include resolution, search, or navigation. See Astro's content collections docs for the routing pattern.

TanStack Start

vite.config.ts
src/routes/docs/$.tsx

Generate docs-pages.json at build time by calling createDocsSource().listPages() from a build script and writing each page's slug, urlPath, and globKey (path relative to the catch-all route, POSIX separators).

Vite + @mdx-js/rollup (works for Vue, Solid, Svelte starters)

vite.config.ts

Nuxt

nuxt.config.ts

SvelteKit + mdsvex

svelte.config.js

Pattern for any other host

If your framework's MDX integration accepts a remark plugin list, leadtype works. Three pieces every time:

  1. Add createMdxSourcePlugins() to the remark list so <include> and <ExtractedTypeTable> resolve at build time.
  2. Implement components against the tag types from leadtype/mdx.
  3. Call createDocsSource() if you want navigation, search, or programmatic page loading.

Implement the tag components

Import prop types from leadtype/mdx and implement against your framework:

lib/mdx-components.tsx

The full tag inventory and intersection patterns for React, Vue, Svelte, Solid, and Astro live in leadtype/mdx.

Build the sidebar from navigation

components/sidebar.tsx

Each page carries a toc field (DocsTableOfContentsItem[]) you can render as an "On this page" rail.

Match heading slugs

source.loadPage().toc uses slugifyDocsHeading to derive anchor IDs. Your rendered heading components need matching id attributes:

Wire Heading2 (and h3, etc.) into your mdxComponents map. Authors can still pin an explicit id on a heading.

Build a search index

app/api/search/route.ts

For provider-specific search (Vercel AI, TanStack, Cloudflare), feed the bundle into a leadtype/search/* adapter — see Add search.

Troubleshooting

  • <include> tags survive into the rendered output. You forgot to add createMdxSourcePlugins() to your MDX compiler's remark plugin list.
  • <ExtractedTypeTable> renders unresolved. The source preset converts these to <TypeTable> only when extractTypeFromFile succeeds. Make sure typescript is installed in the docs app and typeTableBasePath resolves to the project that contains the type.
  • TOC links don't scroll. Rendered heading IDs don't match. Wire slugifyDocsHeading into your heading components.
  • Sidebar order doesn't match llms.txt. Your app and the CLI are loading different docs.config.ts files. Centralize the config and import it in both.

Reference