Skip to main content
better-router defaults to server rendering plus client hydration, then SPA navigation.

First request

  1. Hono matches the route.
  2. Layout loaders run outside-in, then the page loader.
  3. React renders HTML with renderToString.
  4. Loader data is written to window.__BR_DATA__.
  5. The browser hydrates #app.
The HTML document comes from src/routes/_document.tsx or the default shell. Client navigations leave that shell in place and only swap #app. See Layouts.

Later navigation

<Link href="/about"> intercepts the click (plain left click, no modifier keys), requests JSON with x-better-router-navigate: 1, and swaps the page component.
Programmatic navigation:
<Form> uses the same JSON channel. Island pages and spa = false routes do a full document load instead.

Modes

Island pages and regular pages can live in the same app. Navigating to an island page does a full load because there is no SPA payload for that route.

CSS and client-only code

Import CSS from a layout or document. Vite injects it in dev; the client build emits hashed assets.
Guard browser-only APIs:
Do not import Node modules or Worker bindings helpers from a .tsx page. Put that work in .server.tsx.

Payload

window.__BR_DATA__ is the SPA payload:
useLoaderData() reads props. Layouts receive their own layoutProps[i] as React props, not through the hook.