Documentation
Docs
Introduction
Getting Started
Tutorial: build an app
Core Concepts
Routing
Server-Side Rendering
Mimir, state management
Pages & Layouts
API Routes
Styling & Theming
Building & Deploying
API Reference
Guides
Recipes
FAQ (use cases)
Programmatic routes
app.page(path, component, meta) is just a function call, so you can register routes in a loop. This is
how you get fully server-rendered, individually-cached pages for a known set of data, blog posts, docs
pages, product pages, without writing one file per item.
The pattern
Given data with a stable list of entries, register one route per entry, all sharing a single page component:
This is the exact technique the docs site you are reading uses. Each /docs/... URL is a static route
(no :param), so each gets a full cached SSR render on first hit (or eagerly at startup), great for
first paint and SEO, while the shared DocPage component, selected by useRouter().path, makes navigation
between docs instant on the client.
Why not one catch-all [...path]?
A catch-all (/docs/*path) would serve every doc with one shell route. That works, but the page renders
on the client (the server returns a shell), so you lose the full server render and the SSR cache for each
document. Registering one static route per known slug gives you:
- a cached, fully-rendered HTML page per URL (better first paint, better SEO),
- correct per-page
<title>and head tags from each route'sssr(), - the same instant client navigation afterwards (the shared component reads the path).
Use a catch-all when the set is unbounded or unknown (arbitrary user paths); use a per-entry loop when the set is known at build/startup (a docs index, a CMS export, a product list loaded at boot).
Sharing one component, selecting by path
The shared page reads the current path and renders the matching content from the in-memory data:
Because docsData is embedded in the bundle (imported at module load), every navigation is a local lookup,
no network request to fetch the next doc.
Generating the page key
All looped routes share one client chunk (page: "docs.tsx"), since they render the same component. If you
generate routes for different components, give each its own page key matching its manifest entry.
When data changes at runtime
If your route set can change while the server runs (a new post is published), you have two options:
- Re-register on a restart , simplest; the route set is fixed per process.
- Use a catch-all for the dynamic tail and resolve content per request, accepting the shell render.
For content that changes rarely (docs, marketing, a product catalogue refreshed on deploy), the startup loop is the sweet spot: static, cached, SEO-perfect routes with zero per-file boilerplate.
That completes Routing. Next: the SSR model in depth, SSR overview.