Docs

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 via c.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 /files won't match /files/[...path]).
  • [[lang]].tsx → an optional segment: it matches both with and without the segment. [[lang]]/about.tsx serves /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 (so n optionals → 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.tsx wraps its directory; nesting them builds a layout chain (this docs sidebar is a nested layout).
  • _404.tsx renders unmatched paths.
  • _error.tsx is 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 _error in place (all five adapters). It receives the serialized error as { data: { name, message } } (never the stack); a thrown Response (e.g. a guard redirect) 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      →  /pricing

A 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.
Proudly built with Nifra — server-rendered on Cloudflare Pages.MIT