Routing
Routes are files under routes/. The file path is the URL — no route config to maintain.
Conventions
index.tsx→ the parent path;about.tsx→/about.[id].tsx→ a dynamic segment:id(read viac.params.id/ the loader).[...path].tsx→ a catch-all capturing the rest of the URL into one param (params.path="a/b/c"). Must be the last segment; matches one or more segments (so/fileswon't match/files/[...path]).[[lang]].tsx→ an optional segment: it matches both with and without the segment.[[lang]]/about.tsxserves/about(params.lang === undefined) and/:lang/about— handy for an optional locale prefix. It expands to one route per combination, all sharing the page + layout chain (sonoptionals →2ⁿpatterns).(group)/→ a route group: the folder organizes routes (and can hold its own_layout.tsx) without adding a URL segment — e.g.(marketing)/pricing.tsx→/pricing._layout.tsxwraps its directory; nesting them builds a layout chain (this docs sidebar is a nested layout)._404.tsxrenders unmatched paths._error.tsxis the segment's error boundary. On the server — if a route's loader or shell render throws — the nearest_error(in the route's ancestor chain) renders in its place, wrapped by the layouts at/above that segment, at status 500 (served non-hydrated). On the client — a render error during navigation/interaction is caught by the nearest boundary, which renders_errorin place (all five adapters). It receives the serialized error as{ data: { name, message } }(never the stack); a thrownResponse(e.g. a guardredirect) passes through.
TS
routes/
_layout.tsx wraps every page (chain: outer → inner)
_error.tsx error boundary (a loader throws → renders here, 500)
index.tsx → /
about.tsx → /about
users/
[id].tsx → /users/:id dynamic segment
files/
[...path].tsx → /files/*path catch-all (the rest of the path)
[[lang]]/ optional segment — matches WITH and WITHOUT it
docs.tsx → /docs AND /:lang/docs
(marketing)/ route group: organizes + can hold its own _layout,
_layout.tsx but contributes NO URL segment
pricing.tsx → /pricingA route
Each route default-exports a component; an optional meta export drives <head> (applied on SSR and on client navigation). Add a loader for data — see Loaders & actions.
TS
// routes/users/[id].tsx
export const meta = { title: "User" } // injected into <head> (SSR + client nav)
export default function User(props: { data: LoaderData<typeof loader> }) {
return <h1>User {props.data.id}</h1>
}Catch-all routes
A [...name].tsx segment matches the rest of the path and hands it to your loader as a single string param — ideal for docs/CMS trees, file browsers, or a custom fallback. It must be the final segment.
TS
// routes/files/[...path].tsx → matches /files/a, /files/a/b/c.txt, …
export async function loader({ params }) {
const path = params.path // "a/b/c.txt" — the matched tail, as one string
return { file: await read(path) }
}
// A catch-all needs ≥1 segment (/files alone won't match) and must be the last segment.