| name | frontend-route-layout |
| description | Use when creating or modifying route-level layouts, layout routes, shared page wrappers using TanStack Router's Outlet pattern, or when a component wraps multiple pages |
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
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
import { createFileRoute } from "@tanstack/react-router";
import { Page_Login } from "@/pages/Page_Login/Page_Login";
export const Route = createFileRoute("/_auth/login")({
beforeLoad: async () => { },
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
beforeLoad guard
- 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.tsx or a parent layout
Common Mistakes
Wrapper Components Instead of Layout Routes
export const Page_Login = () => (
<AuthLayout>
<LoginForm />
</AuthLayout>
);
export const Page_SignUp = () => (
<AuthLayout>
<SignUpForm />
</AuthLayout>
);
export const Page_Login = () => <LoginForm />;
Missing Outlet
function ProtectedLayout() {
return <Nav />;
}
function ProtectedLayout() {
return (
<>
<Nav />
<Outlet />
</>
);
}
Wrong createFileRoute Path
createFileRoute("/login")
createFileRoute("/_auth/login")
Layout Height Contract
Layout routes define the container size; child pages use relative heights:
function ProtectedLayout() {
return (
<div style={{ height: `calc(100vh - ${NAV_HEIGHT}px)` }}>
<Outlet />
</div>
);
}
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
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