> ## Documentation Index
> Fetch the complete documentation index at: https://better-router.bansal.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Layouts

> Wrap pages with _layout.tsx, load shared data from _layout.server.tsx, and own the HTML document.

A `_layout.tsx` file wraps every page in that directory and below. Layouts compose: `src/routes/_layout.tsx` wraps `src/routes/users/_layout.tsx` wraps `users/[id].tsx`.

```
src/routes/
  _layout.tsx                 # wraps every page
  _document.tsx               # optional HTML shell
  index.tsx
  users/
    _layout.tsx               # wraps /users/*
    [id].tsx
```

```tsx title="src/routes/_layout.tsx" theme={null}
import type { ReactNode } from "react";
import { Link } from "better-router/react";

export default function Layout({ children }: { children: ReactNode }) {
  return (
    <div>
      <nav>
        <Link href="/">Home</Link>
        <Link href="/projects">Projects</Link>
      </nav>
      {children}
    </div>
  );
}
```

<Warning>
  Do not return `<html>` or `<body>` from a layout. That belongs in `_document.tsx`. A layout that emits a second `<html>` produces invalid markup and breaks hydration.
</Warning>

## Layout loaders

Pair a layout with `_layout.server.tsx` to load shared data (current user, workspace, nav counts). The returned object is passed as props to **that** layout only — not to child pages. Pages still need their own loader if they need the same data.

```ts title="src/routes/_layout.server.tsx" theme={null}
import { getEnv } from "better-router";

export async function loader() {
  return { appUrl: getEnv().APP_URL };
}
```

```tsx title="src/routes/_layout.tsx" theme={null}
import type { ReactNode } from "react";
import { Link } from "better-router/react";

export default function Layout({
  children,
  appUrl,
}: {
  children: ReactNode;
  appUrl?: string;
}) {
  return (
    <div>
      <p>{appUrl ?? "better-router"}</p>
      <Link href="/">Home</Link>
      {children}
    </div>
  );
}
```

`redirect()` from a layout loader works. A common pattern is: public marketing layout at `src/routes/(marketing)/_layout.tsx`, authenticated layout at `src/routes/(app)/_layout.tsx` that redirects to `/login`.

Layout loaders run **before** the page loader, outside-in.

## Document

`src/routes/_document.tsx` is the HTML shell. It owns `<html>`, `<head>`, and `<body>`. Layouts wrap the page **inside** `#app`. The document wraps the whole response.

The file is optional and **root-only**. Nested `_document.tsx` files are ignored. If you omit it, the plugin uses a default shell that already includes `#app`, the `window.__BR_DATA__` payload script, and the client runtime.

Add a custom document when you need a title, fonts, or CSS on every HTML response — including `_error.tsx` and `_404.tsx`. Client `<Link>` navigations do not re-render the document; they only swap `#app`.

```tsx title="src/routes/_document.tsx" theme={null}
import type { ReactNode } from "react";

export default function Document({
  children,
  stateScript,
  clientSrc,
  devScripts,
}: {
  children: ReactNode;
  stateScript: string;
  clientSrc: string;
  devScripts?: ReactNode;
}) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>Acme</title>
        <link rel="icon" href="/favicon.ico" />
        <link rel="stylesheet" href="/styles.css" />
        {devScripts}
      </head>
      <body>
        <div id="app">{children}</div>
        <script dangerouslySetInnerHTML={{ __html: stateScript }} />
        <script type="module" src={clientSrc} />
      </body>
    </html>
  );
}
```

| Prop          | Role                                                                    |
| ------------- | ----------------------------------------------------------------------- |
| `children`    | Rendered page and layouts. Put this in `<div id="app">`.                |
| `stateScript` | Serialized `window.__BR_DATA__` payload. Keep as an inline `<script>`.  |
| `clientSrc`   | Client runtime URL. Keep as `<script type="module" src={clientSrc} />`. |
| `devScripts`  | Vite client and React Refresh in dev. Drop this and HMR breaks.         |

You must keep `#app`, `stateScript`, `clientSrc`, and `devScripts`. Dropping them breaks hydration or Vite HMR.

<Note>
  The framework currently always passes `head: null`. Put `<title>`, Open Graph tags, and CSS in this file (or inside layouts with a `<head>` via a library). Per-page `<title>` is not a first-class API yet — set it in the page with `document.title` after mount, or keep one product title in the document.
</Note>

Static files referenced as `/favicon.ico` live in `public/`.

## Errors and 404

`_error.tsx` and `_404.tsx` are not layouts. They replace the page (no layout wrappers). See [Errors](/errors).
