Frontend: Route Layouts (TanStack Router)
Route layouts are layout routes that wrap a group of child routes with shared UI (navigation, sidebars, theme wrappers). They use TanStack Router's file-based _prefix convention and render children via <Outlet />.
CRITICAL: Never use wrapper components for layouts. If multiple routes share a visual frame (nav, sidebar, container), it MUST be a layout route — not a component that each page imports and wraps around its content.
Layout Route Pattern
File Convention
Layout routes use underscore _ prefix — this creates a route node with NO URL path segment:
src/routes/ ├── _auth.tsx # Layout: auth pages (no URL segment) ├── _auth/ │ ├── login.tsx # URL: /login (not /_auth/login) │ └── signup.tsx # URL: /signup ├── _protected.tsx # Layout: authenticated pages ├── _protected/ │ ├── index.tsx # URL: / │ └── $organizationId/ # URL: /$organizationId/...
The _ prefix is pathless — child routes get clean URLs. _auth/login.tsx resolves to /login, not /_auth/login.
Layout Route File
// src/routes/_auth.tsx
import { createFileRoute, Outlet } from "@tanstack/react-router";
import { theme as antdTheme } from "antd";
export const Route = createFileRoute("/_auth")({
component: AuthLayout,
});
function AuthLayout() {
const { token } = antdTheme.useToken();
return (
<div style={{ /* shared layout styles */ }}>
{/* Shared UI: sidebars, navs, decorations */}
<Outlet /> {/* Child route renders here */}
</div>
);
}
Child Route File
// src/routes/_auth/login.tsx
import { createFileRoute } from "@tanstack/react-router";
import { Page_Login } from "@/pages/Page_Login/Page_Login";
export const Route = createFileRoute("/_auth/login")({
beforeLoad: async () => { /* guards */ },
component: Page_Login,
});
Page components become pure content — no layout wrapping, no shared chrome.
Existing Layout Routes
| Layout | File | Purpose | Children |
|---|---|---|---|
_protected | _protected.tsx | Auth guard + nav + sized container | All authenticated pages |
_auth | _auth.tsx | Auth page frame (carousel + form split) | /login, /signup |
When to Create a Layout Route
Create a layout route when:
- •2+ routes share the same visual wrapper (nav, sidebar, split layout)
- •Routes need a common
beforeLoadguard - •A group of pages needs a shared container/theme treatment
Do NOT create a layout route when:
- •Only one page uses the layout → inline it in the page component
- •The "layout" is just a provider → put it in
__root.tsxor a parent layout
Common Mistakes
Wrapper Components Instead of Layout Routes
// ❌ WRONG — wrapper component imported per page
export const Page_Login = () => (
<AuthLayout>
<LoginForm />
</AuthLayout>
);
export const Page_SignUp = () => (
<AuthLayout>
<SignUpForm />
</AuthLayout>
);
// ✅ CORRECT — layout route with Outlet // _auth.tsx provides the layout // Page_Login and Page_SignUp are pure content, no wrapper export const Page_Login = () => <LoginForm />;
Missing Outlet
// ❌ WRONG — layout without Outlet (children never render)
function ProtectedLayout() {
return <Nav />;
}
// ✅ CORRECT — Outlet renders child route
function ProtectedLayout() {
return (
<>
<Nav />
<Outlet />
</>
);
}
Wrong createFileRoute Path
// ❌ WRONG — path doesn't match file location
// File: src/routes/_auth/login.tsx
createFileRoute("/login")
// ✅ CORRECT — matches file-based route tree
createFileRoute("/_auth/login")
Layout Height Contract
Layout routes define the container size; child pages use relative heights:
// Layout route provides sized container
function ProtectedLayout() {
return (
<div style={{ height: `calc(100vh - ${NAV_HEIGHT}px)` }}>
<Outlet />
</div>
);
}
// Page uses height: 100% (NEVER recalculate viewport)
function Page_Example() {
return <div style={{ height: "100%" }}>{/* content */}</div>;
}
See frontend-page-layout for full height calculation rules.
Route Tree Regeneration
After creating/moving route files, regenerate the route tree:
pnpm --filter my-vite-app dev # Auto-regenerates on file change # Or manually trigger by saving any route file during dev server
The generated routeTree.gen.ts is auto-managed — never edit it manually.
Related Skills
- •frontend-routing — Route naming, params, guards, navigation
- •frontend-page-layout — Page-level height calculations and flex patterns