Docs
Remix Guides
A runnable Remix app for the in-progress Remix 3 guide docs. Use it to browse, write, and test narrative docs with live Remix examples.
The guides are the hand-authored docs: Start Here, Core App Structure, Server Runtime, and the rest of the chapter sequence. The generated API reference lives in ../api.
#Where things live
app/actions/controller.tsx— top-level asset route handling.app/actions/docs/chapters/*.md— guide chapters.app/actions/docs/markdown/render.tsx— chapter metadata and::framerendering built on the shared unified/remark/rehype pipeline.app/actions/docs/markdown-chapters.tsx— chapter loading, ordering, slugs, navigation, summaries, and mtime-based render caches.app/actions/docs/layout.tsxandchapter-navigation.tsx— guides-specific content and navigation rendered inside the shell from../shared/ui/.app/actions/docs/public/— browser behavior owned by the Guides route, including its active-chapter animation.app/actions/docs/examples/— frame handlers used by chapters. Each chapter group'spublic/directory contains its browser-hydrated demo modules and their local dependencies.app/actions/public/— root-owned browser entrypoint, development refresh module, and stylesheets.app/assets.ts— the source asset server and root entry hrefs/preloads, configured around colocatedpublic/directories.app/middleware/render.ts— the request-scopedrender()helper and frame resolver.app/routes.tsandapp/router.ts— the typed route contract and controller wiring.app/ui/— shared UI used across routes.public/— guides-only static files served as-is by the static middleware. Shared docs assets live in../shared/assets/.
#How chapters work
Chapter files live in app/actions/docs/chapters/. The file name controls order, chapter label, URL slug, and previous/next links:
01-start-here.md -> Chapter 1 -> /start-here/
Each chapter needs frontmatter. Level-2 headings power the docs index and "On this page" navigation. IDs are generated from heading text unless you add an explicit one:
---
title: Start Here
description: A high-level introduction to Remix.
---
An optional chapter introduction can go here.
## Build your first page
## Stable custom anchor {#custom-anchor}
Code fences support filename headers and line highlighting:
```tsx filename=app/actions/projects/create.tsx lines=[4-5,8]
export async function createProject(request: Request) {
let formData = await request.formData()
let name = String(formData.get('name') ?? '').trim()
if (name === '') {
return new Response('Project name is required', { status: 400 })
}
return new Response(`Created ${name}`)
}
```
#Adding frame examples
Use a frame directive in Markdown:
::frame{src="/examples/17-markdown-style-demo/counter/"}
The examples controller maps /examples/:chapter/:example/ to app/actions/docs/examples/<chapter>/<example>.tsx and dynamically imports the module, so no route changes are needed. The validator requires the :chapter segment to match the chapter directory name (e.g. 17-markdown-style-demo) so examples stay scoped to the chapter that references them. Example directories are prefixed with the chapter order number to match the chapter file name.
At render time, markdown.tsx turns the directive into <Frame src="..." />. The render middleware resolves that frame by doing an internal router.fetch() for the frame URL, so examples are normal Remix routes that return normal Response objects.
#Demos with code
A "demo with code" shows a live, hydrated component next to its own highlighted source. It takes three co-located files:
-
The demo code — a
.demo.tsxmodule inside the frame handler'spublic/directory that exports the component as a named export whose name matches the function name:app/actions/docs/examples/17-markdown-style-demo/public/counter.demo.tsximport { css, on } from 'remix/ui' import type { Handle } from 'remix/ui' export function Counter(handle: Handle) { let count = 3 return () => ( <button mix={[ on('click', () => { count++ handle.update() }), css({ borderRadius: '999px', padding: '0.7rem 1rem' }), ]} type="button" > Count: {count} </button> ) } -
The frame handler — a
<example>.tsxmodule that exports ahandlerbuilt withdemoWithCode, pointing at the demo module and its component:app/actions/docs/examples/17-markdown-style-demo/counter.tsximport { demoWithCode } from '../demo-with-code.tsx' import { Counter } from './public/counter.demo.tsx' let demoUrl = new URL('./public/counter.demo.tsx', import.meta.url) export const handler = demoWithCode(demoUrl, Counter) -
The shared shell —
app/actions/docs/examples/demo-with-code.tsxexportsdemoWithCode(which loads and highlights the.demo.tsxsource, hydrates the component viaclientEntry, and renders the preview + source) and theDemocomponent that lays them out. You don't touch this per example.
The named export matters: demoWithCode resolves the client entry from the function's name, so the export name and the function name must be the same token (e.g. export function Counter), not a default export.
For route-style frames that need full control, export a named handler that returns a Response directly instead of using demoWithCode.
#Commands
Run from the repo root or from docs/guides/.
pnpm install # once, from the repo root
pnpm --filter remix-guides run dev # watch + serve
pnpm --filter remix-guides run start # serve once
pnpm --filter remix-guides run validate # check frame URLs and example files
pnpm --filter remix-guides run prerender # build the static site in docs/guides/build/site
pnpm --filter remix-guides run prerender:serve # serve the static site
pnpm --filter remix-guides run test
pnpm --filter remix-guides run typecheck
The dev server listens on http://localhost:44100 by default. Set PORT to override. The guides index is served at /.
#Static site
pnpm --filter remix-guides run prerender renders the docs index, every chapter, referenced frame examples, browser modules, styles, and public files into docs/guides/build/site/, then builds the Pagefind search index. The output uses directory index.html files for clean URLs and can be served directly by GitHub Pages or another static host.
Production deployment is owned by the remix-run/remix-guides-docs GitHub Pages workflow. It checks out this repository, runs the prerender command, and uploads the static output; server.ts and its middleware run locally only. The Pagefind version is pinned in this repository's pnpm catalog.
The development server serves an existing search index from docs/guides/build/site/assets/pagefind/. Run prerender once to enable search during development, and rerun it when the guide content changes to refresh the index.
Pass --dir to write into another directory:
pnpm --filter remix-guides run prerender --dir ../../remix-guides-site
Sites hosted beneath a URL prefix can pass --base-path or set REMIX_GUIDES_BASE_PATH:
pnpm --filter remix-guides run prerender --base-path /remix-guides-docs
The base path is applied to generated site URLs without changing the output directory layout. The output directory is cleared before each build so removed chapters and outdated fingerprinted assets cannot survive into the next Pagefind index or deployment.