_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
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.
src/routes/_layout.server.tsx
src/routes/_layout.tsx
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.
src/routes/_document.tsx
You must keep
#app, stateScript, clientSrc, and devScripts. Dropping them breaks hydration or Vite HMR.
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./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.