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)
Dynamic routes
Dynamic routes match a shape of URL and expose the variable parts as params. They are how you build
/blog/:slug, /users/:id, or a docs catch-all like /docs/*path.
Single dynamic segment , [param]
pages/blog/[slug].tsx -> /blog/:slugA [param] matches exactly one path segment. /blog/hello-world matches; /blog/2024/hello does not
(use a catch-all for that).
Nested routes under a [param] , /items/:id and /items/:id/edit
Because :id matches exactly one segment, /items/:id does not match /items/123/edit. They are
two separate routes , each is its own page file (and gets its own client chunk). This is the way to build
a detail page plus an edit page:
pages/items/[id].tsx -> /items/:id (detail)
pages/items/[id]/edit.tsx -> /items/:id/edit (edit)The flat file [id].tsx and the directory [id]/ coexist in the same folder. Both pages read the same param:
Do not try to serve /items/:id/edit from the /items/:id page by inspecting the path , :id will
never swallow the trailing edit segment, so the match simply fails and you get a 404. Add the edit.tsx
file and let the router match it.
Always
ekko build --clientafter adding a page. A page is wired to its route through the build manifest. If a route has no built chunk it logs a[rune] route … has NO client chunk …warning at startup and renders blank (or full-reloads on navigation) , a rebuild fixes it. A path with no matching page file at all is a normal 404.
Catch-all , [...param]
pages/docs/[...path].tsx -> /docs/*pathA catch-all matches the rest of the path, one or more segments, and exposes them as a string (or array,
depending on how you read it). The docs site you are reading uses exactly this shape: every /docs/... URL
is served by one page module that picks the document from the current path.
Reading params, query, and path
The router gives you everything about the current URL:
See The router for the full hook surface.
Dynamic routes are rendered as a shell, then hydrated
Static routes with an ssr() are fully server-rendered and cached. Dynamic routes (:param, *catch)
are not pre-cached, their content depends on the URL, so the server returns a shell (the layout +
hydration data with props.params, props.query, props.path) and the client renders the page using those
props. You can still export an ssr() to set the document title and head for the shell.
Practically:
- Title/SEO for a dynamic page: return it from
ssr()(it runs per request for the shell). - The body of a dynamic page renders on the client from
params/query, or you fetch data after
hydration (see Data fetching).
If you have a fixed, known set of dynamic values (e.g. a finite list of blog slugs), you can register one static route per value and get full cached SSR for each, this is exactly how the docs generate a route per page. See Programmatic routes.
Specificity recap
Given these files:
pages/blog/new.tsx -> /blog/new (static, priority 0)
pages/blog/[slug].tsx -> /blog/:slug (dynamic, priority 1)
pages/blog/[...rest].tsx -> /blog/*rest (catch-all, priority 2)/blog/new hits the static route; /blog/hello hits :slug; /blog/2024/03/post hits *rest. rune sorts
by priority so this "just works".
Next: the client router API, The router.