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.

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 dev

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.

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 tailwindcss

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.

heyo-docs.config.ts
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:

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.

vite.config.ts
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 })],
});
app/app.css
@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.

app/routes/docs.tsx
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} />;
}
app/routes.ts
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:

text
/_heyo-docs/openapi/<group>/<tag>/<operation>.json

During 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.

Next steps