Routes

A file in src/routes/ defines a URL, its page and any request handlers it needs.

updated in 0.9.3
On this page
/posts/42routes/posts/[id].tsxposts/[id].jsxid = "42"
/posts/newroutes/posts/new.tsxposts/new.jsxstatic wins
/aboutroutes/about.tsxabout.jsxprerendered

Pages and handlers

A default export is the React page. Without an explicit GET handler, Goribu renders it directly:

src/routes/about.tsx
export const meta = { title: "About" };

// This page has no request-time data to load.
export default function AboutPage() {
  return <main><h1>About our app</h1></main>;
}
src/routes/about.jsx
export const meta = { title: "About" };

// This page has no request-time data to load.
export default function AboutPage() {
  return <main><h1>About our app</h1></main>;
}
  • Request data: Export a named HTTP handler when the response needs request data.
  • Page response: Return res.render(props) to render the page in the same route file.
  • API endpoints: A route with only handlers does not need a React component.

File-based URLs

FileURL
src/routes/index.tsxsrc/routes/index.jsx/
src/routes/about.tsxsrc/routes/about.jsx/about
src/routes/posts/index.tsxsrc/routes/posts/index.jsx/posts
src/routes/posts/new.tsxsrc/routes/posts/new.jsx/posts/new
src/routes/posts/[id].tsxsrc/routes/posts/[id].jsx/posts/42
  • Extensions: Route files may use .js, .jsx, .ts, or .tsx.
  • Index routes: An index file maps to its containing folder.
  • Excluded files: TypeScript declaration files are ignored so they can be colocated.
  • Symlinks: Route discovery does not follow symlinks.

Dynamic parameters

Square brackets capture one URL segment. You can read the value as a string from req.params:

src/routes/hello/[name].tsx
import type { Handler } from "goribu/server";

type Props = { name: string };

export const GET: Handler<Props> = (req, res) => {
  return res.render({ name: req.params.name });
};

export default function HelloPage({ name }: Props) {
  return <h1>Hello, {name}.</h1>;
}
src/routes/hello/[name].jsx
export const GET = (req, res) => {
  return res.render({ name: req.params.name });
};

export default function HelloPage({ name }) {
  return <h1>Hello, {name}.</h1>;
}
  • Parameter names: Dynamic parameter names may contain letters, digits and underscores. Each name must be distinct within one route.
  • Unsupported segments: Catch-all and optional segments are not supported.

Pre-rendered pages

To run a GET handler at build time, export prerender = true on a fixed-path page without middleware. This page captures the build timestamp:

src/routes/build-info.tsx
import type { Handler } from "goribu/server";

type Props = { builtAt: string };

export const prerender = true;

export const GET: Handler<Props> = (req, res) => {
  return res.render({ builtAt: new Date().toISOString() });
};

export default function BuildInfoPage({ builtAt }: Props) {
  return <p>Built at {builtAt}</p>;
}
src/routes/build-info.jsx
export const prerender = true;

export const GET = (req, res) => {
  return res.render({ builtAt: new Date().toISOString() });
};

export default function BuildInfoPage({ builtAt }) {
  return <p>Built at {builtAt}</p>;
}
  • Automatic candidates: A page will be automatically pre-rendered when its URL is known at build time, has no HTTP handlers or selected middleware and has not opted out explicitly.
  • Production delivery: After a successful pre-render, Cloudflare Static Assets serves the HTML and saved navigation data. Document loads, client navigation and prefetch avoid Worker invocations and their cost.

Pre-rendering fixes the server output at build time. The page can still hydrate, use React state and run client-side navigation.

Use a route export to make the choice explicit:

Route option
export const prerender = false;
Route option
export const prerender = false;
  • Opt out: prerender = false keeps an otherwise static page on the Worker.
  • Require static output: prerender = true requires static output and fails the build if Goribu cannot produce it.
  • GET at build time: With prerender = true, GET runs during the production build instead of on each request. Visitors receive the saved page, which stays unchanged until the next build.
  • Incompatible routes: prerender = true cannot be combined with non-GET handlers, dynamic segments or middleware.
  • Fixed data: Pre-rendered HTML and navigation data share the same build-time props and metadata. Changing query strings does not rerun an explicitly pre-rendered GET handler or server metadata functions.
  • Development: Pages use the Worker during development for freshness.
  • Build fallback: A static candidate that cannot render at build time normally falls back to the Worker with a build message.
  • Suspense failures: Since 0.9.3, a candidate whose Suspense boundary throws during the build stays on the Worker with a build warning, rather than shipping only its fallback. With prerender = true, the build fails.
  • Separate actions: A static page can submit a Form to a separate action route.

Routing rules

  • Required exports: Every route needs a page or at least one HTTP handler, but it can have both.
  • Conflicting routes: Two files with the same URL shape fail the build. posts/[id].tsxposts/[id].jsx and posts/[slug].tsxposts/[slug].jsx conflict.
  • Underscore names: Names beginning with _ are not URLs. Underscore-prefixed folders exclude their subtree.
  • Server helpers: Keep .server.* helpers outside src/routes/ and import them from handlers or middleware.
  • Browser code restrictions: Route files use explicit named re-exports; export *, bare .server.* side-effect imports and direct eval are rejected in browser-reachable route code. See Server-only work.
  • Reserved URLs: /_goribu/ is reserved for framework assets and protocols.
  • Public HTML: Do not place public HTML files at URLs already owned by routes.
  • Prebuilt 404: Since 0.9.3, 404.html is handled by Goribu's prebuilt 404 page, so customize _404 instead.
  • Special route files: _404, _500, and folder-level _middleware have special roles under routes. See Errors and Request handlers.
  • App-wide Document: The optional Document file is located separately in src/setup/_document.tsxsrc/setup/_document.jsx. See Rendering.

HTTP defaults

  • GET: A page has an implicit GET. An explicit handler answers its own method.
  • HEAD: Goribu derives HEAD from GET when needed.
  • OPTIONS: Automatic OPTIONS returns 204 with an Allow header.
  • Unsupported methods: Goribu returns 405 for unsupported methods.
  • Unmatched URLs: Unmatched URLs return 404. Since 0.9.3, production serves URLs that no Worker route can match from Static Assets without invoking the Worker. See Which requests run the Worker.
  • Trailing slashes: Trailing slashes redirect to the slash-free URL, except for /. Static assets use the same canonical shape.