React Router
Create a React Router documentation site with Heyo Docs, or add the smallest integration to an existing app.
For a new documentation site, use create-heyo-docs. It creates the complete
React Router Framework Mode application, including the content pipeline, MDX
route, theme, icons, static prerendering, and optional deployment files. This
is the recommended installation path because the generated route modules keep
the browser and server data boundaries aligned.
Recommended: create a project
Run the creator and select the React Router template. Passing the template on the command line makes the command repeatable; omit the flags to answer the same questions interactively.
create-heyo-docs creates a standalone documentation application; it does not
add files to an existing product. The wizard can also choose a deployment
overlay, theme, package manager, and whether to install dependencies. Once the
server starts, edit content/index.mdx and heyo-docs.config.ts to make the
site your own. The starter derives its initial Documentation sidebar from the
MDX files; add groups only when you need custom ordering.
Manual, minimal installation
Use this path only when documentation must live inside an existing React Router Framework Mode application. It intentionally leaves out the optional OpenAPI proxy, Markdown mirrors, SEO routes, icon mapping, and persisted theme switcher. Those production-ready additions are already present in a project generated by the creator.
1. Install the runtime and theme dependencies
The following assumes an existing React Router application using Vite and Tailwind v4:
2. Define the documentation model
Create heyo-docs.config.ts at the application root. The paths in pages are
relative to content and do not include the .mdx extension.
Then add the first page at content/index.mdx:
3. Register the Vite plugin and theme
The plugin turns the MDX directory into virtual modules and refreshes the site when content changes. Keep it after the React Router plugin.
4. Mount a catch-all documentation route
Import the virtual page list and give DocsApp the current URL. A minimal
route can use ordinary anchors; provide React Router's Link, the generated
icon set, and a theme provider when you want the full template behaviour.
Run bun run dev. When you need static deployment, server-side metadata,
OpenAPI reference pages, or the Try it action, start from the generated
template and bring over its route modules rather than recreating those pieces
one at a time.
Development feedback
While bun run dev is running, saving an MDX file refreshes the rendered page
without restarting the server. The template's prerender setting applies only
to react-router build: production content changes still require a new build
and deployment.
Rendering strategy for the React Router template
This rendering strategy applies to the React Router template generated by
create-heyo-docs.
The React Router template prerenders every known documentation route at build
time while retaining a minimal runtime capability for the optional Try it
API proxy. Documentation is served as static HTML and assets from the CDN, but
one intentional POST action can still execute securely on the server. This is
a deployment capability, not a static | hybrid | server configuration mode.
flowchart LR A[MDX and OpenAPI sources] --> B[react-router build] B --> C[Prerendered HTML and data files] B --> D[Static endpoint JSON shards] C --> E[CDN] D --> E F[Try it POST] --> G[React Router runtime] G --> H[Declared API server]
Build and rendering
react-router.config.ts keeps ssr: true so a runtime action route is
available, then uses the prerender option to enumerate all MDX and generated
OpenAPI paths. It also prerenders robots.txt, sitemap.xml, RSS, and LLMS
files. The existing *.md mirror is served by its resource route when needed;
it is intentionally outside the documentation HTML prerender list.
The route list comes from documentationPaths() in @heyo-sh/heyo-docs/node. It scans
the configured content directory, loads the configured OpenAPI documents,
applies the same collision rules as the application, and returns the final
public slugs. The build is therefore the single source of truth for both the
sidebar and static deployment output.
Unlisted application routes may still use normal SSR. Heyo Docs itself does not depend on that fallback for documentation pages.
Pages without a loading placeholder
The server imports every MDX page directly, and the documentation body carries no Suspense boundary, so prerendered HTML holds the finished page instead of a placeholder that the browser replaces a moment later.
In the browser, each MDX page stays a separate chunk. Loaders run on the
server, so the route adds a clientLoader that fetches the chunk of the page a
reader opened before the new route is displayed. The current page stays on
screen until the next one can be rendered in full:
Re-export clientLoader wherever the documentation route is mounted again, as
app/routes/page.tsx does for the catch-all route. React Router only runs it
when the route module exports it.
app/entry.server.tsx must render with renderToReadableStream. React
Router carries its hydration data inside a Suspense boundary, and
renderToString cannot complete one: the document is sent with the boundary
marked as failed, and the browser then waits for a stream that never
arrives. The page looks right and never hydrates, which costs you
table-of-contents tracking, client-side navigation, and syntax highlighting,
and resets the sidebar on every link.
OpenAPI data delivery
The client bundle contains only the compact endpoint index needed for navigation and search. Rich endpoint data is emitted as a static JSON asset:
The route loader runs on the server, so during prerendering it resolves the active endpoint and serialises its detailed payload with that route’s static data file. The initial HTML and hydration therefore render the final API-reference UI immediately; there is no compact-first render followed by a visible replacement.
A client-side navigation reads that same data file before React Router displays the destination route, which preserves the guarantee when a reader moves between endpoint pages. The payload includes endpoint-specific schemas, examples, parameters, responses, and security information, but only the OpenAPI components reachable from that operation. The complete schema never reaches the shared browser bundle.
The JSON shard stays a public build artefact for integrations and tooling that start from the compact index.
Dynamic boundary: Try it
POST /heyo-docs-internal/openapi-request is deliberately not prerendered. It
is the only runtime route involved in OpenAPI: it forwards a documented request
to an API server selected from the schema. Keeping that work server-side avoids
browser CORS constraints and lets the handler reject unknown target servers.
The Markdown mirror resource route and POST /heyo-docs-internal/ai-chat also
run on demand. The AI route exists only when ai.chat is configured. Neither
is involved in rendering documentation HTML or loading endpoint data.
Cloudflare and Vercel overlays host this route in their respective runtime; the static pages and JSON assets are still delivered by the platform CDN. If interactive requests are unnecessary, the route can be removed without affecting the generated documentation.
Release and scale characteristics
- A documentation or schema change takes effect after build and deploy.
- Remote OpenAPI sources are build dependencies; use pinned URLs for repeatable releases.
- More endpoints increase build time and static output size, not request-time compute.
- The CDN can cache pages and endpoint JSON independently, so a reader only downloads detailed data for the API operation they view.