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.
bun create @heyo-sh/heyo-docs my-docs --template react-routercd my-docsbun run devcreate-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.
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:
bun add @heyo-sh/heyo-docs @fontsource-variable/figtree @remixicon/react shadcn tw-animate-cssbun add -d @tailwindcss/vite tailwindcss2. 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.
import { heyoDocs } from "@heyo-sh/heyo-docs";
export default heyoDocs({
title: "Acme Docs",
description: "Documentation for Acme.",
content: "content",
theme: "grain",
groups: [
{
group: "Documentation",
sections: [{ section: "Get started", pages: ["index"] }],
},
],
branding: { name: "Acme" },
});Then add the first page at content/index.mdx:
---title: Welcomedescription: Start here.---# WelcomeYour documentation is ready.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.
import { reactRouter } from "@react-router/dev/vite";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
import { heyoDocs } from "@heyo-sh/heyo-docs/vite";
import config from "./heyo-docs.config";
export default defineConfig({
plugins: [tailwindcss(), reactRouter(), heyoDocs({ config })],
});@import "@fontsource-variable/figtree";@import "@heyo-sh/heyo-docs/theme/grain.css";:root { --heyo-docs-font-family: "Figtree Variable", sans-serif;}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.
import { DocsApp, normaliseDocsPathname } from "@heyo-sh/heyo-docs";
import { useParams } from "react-router";
import config from "../../heyo-docs.config";
import { pages } from "virtual:heyo-docs-content";
export default function DocsRoute() {
const { "*": slug } = useParams();
const pathname = normaliseDocsPathname(slug ? `/${slug}` : "/");
return <DocsApp config={config} pages={pages} pathname={pathname} />;
}import { index, route } from "@react-router/dev/routes";
export default [index("routes/docs.tsx"), route("*", "routes/docs.tsx")];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 server for the optional Try it API proxy. The result is a hybrid deployment: documentation is served as static HTML and assets from the CDN, but an intentional POST action can still execute securely on the server.
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.
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:
/_heyo-docs/openapi/<group>/<tag>/<operation>.jsonDuring prerendering, the route loader resolves the active endpoint and serialises its detailed payload with that route’s static data. The initial HTML and hydration therefore render the final API-reference UI immediately; there is no compact-first render followed by a visible replacement.
For client-side navigation, the loader waits for the same-origin JSON shard from the CDN before React Router commits the destination route. This preserves the same visual guarantee when moving between endpoint pages. The JSON includes endpoint-specific schemas, examples, parameters, responses, and security information, but only the OpenAPI components reachable from that operation. It avoids shipping the complete schema in the shared browser bundle.
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 existing Markdown mirror resource route can also run on demand. It is separate from HTML documentation rendering and from the static OpenAPI JSON assets.
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.