Automating Documentation Generation with TypeDoc and MDX
Hand-written API docs go stale the moment someone changes a function signature and forgets the markdown file that describes it. The signature and the description live in two different files, edited by two different habits, and only one of them is enforced by the compiler. Six months later the docs describe a parameter that no longer exists, and a new engineer trusts the docs over the source — because why wouldn't they.
TypeDoc closes that gap by extracting documentation directly from TSDoc comments next to the code, so the signature and its description can never drift — they're the same AST node. Rendering that extracted model as MDX puts it inside your existing Next.js site instead of a separate static-docs tool, which means the same layout, search, and navigation your blog already has. This guide builds one concrete pipeline: a public fetchOrder() function documented with TSDoc, extracted by TypeDoc, rendered as an MDX page, and a CI check that fails the build if a public export ships without a description. For the broader pattern of building the site around this content, see Knowledge base with Next.js and MDX.
Why generated docs and not hand-written
The core argument for generated documentation is not "less typing" — it's single source of truth. A TSDoc comment lives directly above the function it documents, in the same file, reviewed in the same pull request as the signature change. There is no second file to remember to update, which means there is no drift between what the docs say and what the code actually does.
The trade-off is real: TSDoc comments are more constrained than free-form markdown, and generated docs are only as good as the comments developers actually write. A @param tag left blank generates a blank parameter description on the live docs page — TypeDoc extracts faithfully, it does not invent missing prose. That constraint is a feature once you add a CI gate (covered later) that fails the build on missing tags, turning "someone should document this" into "the build fails until you do."
Quick reference
- TSDoc is a subset of JSDoc with a stricter, tool-parseable tag syntax —
@param,@returns,@throws,@example. - TypeDoc walks the compiled TypeScript AST, so it sees the real exported type, not a hand-typed guess of it.
- Generated docs remove drift risk but do not remove the discipline requirement — an empty
@paramtag still generates an empty description. - Keep prose examples (
@example) short and runnable; TypeDoc renders them as code blocks verbatim, untested by default.
Remember this
Generated documentation eliminates drift between signature and description by making them the same source node — it does not eliminate the need for developers to actually write the comment, which is exactly what a CI gate should enforce.
From TSDoc comment to a JSON model
TypeDoc's first job is turning a directory of TypeScript source into a structured JSON model — a tree of "reflections" describing every exported module, class, function, and its parameters, return type, and doc comment. This JSON model is the seam in the pipeline: it decouples "how TypeDoc parses TypeScript" from "how we render that into MDX," which means the render step can change (a new theme, a new site) without touching the extraction step.
Run this as a --json output rather than TypeDoc's default HTML theme — the JSON model is what the MDX generator in the next section consumes, and it is a stable, versioned format independent of TypeDoc's built-in visual theme.
Quick reference
- Point
entryPointsat your public API surface only — internal modules shouldn't leak into generated docs. - Commit
reflections.jsonor regenerate it in CI; treat it as a build artifact, not source of truth for anything else. - TypeDoc respects
@internaland@hiddentags to exclude symbols that are exported for tooling reasons but not public API. - A reflection with an empty comment is not an error by default — that's why the CI gate in a later section exists.
Remember this
TypeDoc's JSON model is the seam that lets the render step change independently of the extraction step — treat it as a versioned build artifact, not a place to hand-edit documentation.
Rendering reflections as MDX pages
With a stable JSON model, generating MDX is a straightforward transform: one MDX file per exported symbol (or one per module, for a smaller API), each frontmatter block carrying the symbol's name and category for your site's navigation, and the body rendering the signature, parameters, and any @example blocks as fenced code.
Because the output is plain MDX, it slots into an existing Next.js content pipeline exactly like a hand-written blog post — same layout, same search index, same dark-mode styles. The generator only needs to run once per build (or on-demand in development) and commit or regenerate the MDX files as a build step, not check them in as hand-edited source.
Quick reference
- Generate one MDX file per public export for granular linking; group into modules only if the API surface is very large.
- Include the
@examplecode block verbatim as a fenced code block —BlogText/MDX renderers already handle syntax highlighting. - Run the undocumented-export check as a required CI job, not an advisory warning — advisory checks get ignored within a quarter.
- Version the generated docs alongside releases so a reader on an older package version sees the matching API, not the latest one.
- If the same public functions are also exposed as a versioned HTTP contract, apply the same required-check discipline described in CI/CD with GitHub Actions and Vercel.
Remember this
A required CI job that fails on any undocumented public export turns "please document your code" from a code-review nag into a merge blocker — that's the only version of this rule that survives past the first busy sprint.
Key takeaway
Build the pipeline against a small real module: write fetchOrder() with a full TSDoc comment (summary, @param, @returns, one @example), run the extraction command, and confirm reflections.json contains a populated comment for it. Then run the MDX generator and confirm the rendered page shows the signature, parameter description, and example exactly as written.
Break it on purpose: add a second exported function with no doc comment at all, re-run the docs:check script, and confirm it exits non-zero with that function's name in the failure output. Pass criterion: the documented function renders a complete MDX page and the undocumented one fails CI by name — not silently, and not as a warning that a busy engineer can ignore.
Related Articles
Explore this topic