TanStack Router 12 ⚛️ Layout Routes — Shared parent layouts
The previous chapters covered the route tree, the path parameters, and the data loaders. This chapter focuses on the structure that organizes those routes into a coherent application: the layout routes. A layout route is a route that renders a shared component (the navigation, the sidebar, the footer, the providers) around its child routes via the <Outlet />. In the TanStack Router, a layout route is a file that ends with .tsx but does not have a component that represents a full page—instead, it renders the <Outlet /> in the layout. The critical distinction is between the layout route (a route with children that renders the shared UI) and the pathless layout route (a route that does not add a URL segment but still provides the shared UI). The exam tests the <Outlet /> and the pathless layout configuration.
Key point: The layout route is a parent route with the children array. The parent’s component renders the shared UI and the <Outlet />. The child routes render inside the <Outlet />. The pathless layout route uses the createFileRoute('/_pathlessLayout') with the leading underscore, which removes the segment from the URL. The nested layout is the natural application structure: the root layout wraps everything, the section layout wraps the section, and the page component is the leaf.
Why layout routes require a specific mental model
The outlet problem. The layout route renders a component. That component must include the <Outlet /> where the child routes render. Without the <Outlet />, the child routes match but never render. The URL changes, but the page shows only the layout. The <Outlet /> is the placeholder; it is the most forgotten piece.
The pathless layout problem. A route with the path /dashboard and a child with the path /dashboard/settings is the standard nested layout. A route that does not add a URL segment but still provides the shared UI is the pathless layout. In the file-based routing, the pathless layout is the file with the leading underscore: _app.tsx becomes the layout for everything under it, and the _app segment is not in the URL. The route is /settings, not /_app/settings .
The layout hierarchy problem. The application can have multiple layouts. The root layout (__root.tsx) renders the HTML shell and the global providers. The pathless layout (_authenticated.tsx) checks the auth and renders the authenticated layout. The section layout (_dashboard.tsx) renders the dashboard sidebar. The page component is the leaf. Each layout wraps the next, and the <Outlet /> is the slot for the child. The hierarchy is the composition.
The context problem. The layout route can provide the context to the child routes via the <Outlet context={...} />. The child reads the context with the useOutletContext() hook. The context is the mechanism for the layout to pass the data (the user, the theme, the settings) to the child without the prop drilling. The Route.useRouteContext() is the TanStack Router’s alternative for the route-level context.
The <Outlet /> with the props problem. The <Outlet /> in the TanStack Router does not accept the child route’s props directly. The layout passes the context via the context prop or the route context. The useOutletContext hook reads it. The useRouteContext hook reads the route-level context, which is the router’s context, not the layout’s.
The trade-off. The layout routes are the natural structure for the shared UI. The pathless layouts are the natural structure for the shared logic that should not appear in the URL. The two are complementary: the layout for the UI, the pathless for the auth guard and the providers.
a. The nested layout route with the <Outlet />
A layout route is a parent route with children. The parent’s component renders the layout and the <Outlet />.
// src/routes/dashboard.tsx
import { createFileRoute, Link, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/dashboard')({
component: DashboardLayout,
})
function DashboardLayout() {
return (
<div className="dashboard">
<aside>
<nav>
<Link to="/dashboard">Overview</Link>
<Link to="/dashboard/settings">Settings</Link>
<Link to="/dashboard/analytics">Analytics</Link>
</nav>
</aside>
<main>
<Outlet />
</main>
</div>
)
}
The /dashboard route is the layout. The <Outlet /> renders the child. The child routes are in the separate files with the /dashboard/settings, /dashboard/analytics paths.
// src/routes/dashboard.settings.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/dashboard/settings')({
component: Settings,
})
function Settings() {
return <h1>Settings</h1>
}
The /dashboard/settings URL matches the dashboard.settings.tsx file. The DashboardLayout renders the sidebar, and the Settings renders inside the <Outlet />. The sidebar remains visible in both the /dashboard and the /dashboard/settings URLs.
b. The pathless layout route
The pathless layout route uses the leading underscore in the file name. The _app.tsx file becomes the layout for the routes under it, but the _app is not in the URL.
// src/routes/_app.tsx
import { createFileRoute, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/_app')({
component: AppLayout,
})
function AppLayout() {
return (
<div>
<GlobalHeader />
<main>
<Outlet />
</main>
<GlobalFooter />
</div>
)
}
The createFileRoute('/_app') path has the _app segment. The plugin recognizes the underscore prefix and treats the route as the layout without the URL segment. The URLs of the child routes are the child paths only, not /_app/child.
The child routes are in the _app directory or the _app.child.tsx files.
// src/routes/_app.settings.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/_app/settings')({
component: Settings,
})
function Settings() {
return <h1>Settings</h1>
}
The /settings URL matches the _app.settings.tsx file. The AppLayout renders, and the Settings renders inside the <Outlet />. The URL is /settings, not /_app/settings.
c. The layout context and the guards
The layout can pass the context to the child routes via the <Outlet context={...} />. The child reads it with the useOutletContext() hook.
// src/routes/dashboard.tsx
import { createFileRoute, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/dashboard')({
component: DashboardLayout,
})
function DashboardLayout() {
const [user, setUser] = useState(null)
return (
<div>
<Sidebar user={user} />
<Outlet context={{ user, setUser }} />
</div>
)
}
The Outlet context={{ user, setUser }} passes the object. The child reads it.
// src/routes/dashboard.settings.tsx
import { createFileRoute, useOutletContext } from '@tanstack/react-router'
export const Route = createFileRoute('/dashboard/settings')({
component: Settings,
})
function Settings() {
const { user, setUser } = useOutletContext()
return <h1>Settings for {user?.name}</h1>
}
The useOutletContext() returns the object from the layout. The child uses it without the prop drilling.
The layout can also be the guard. The auth check in the layout redirects if the user is not logged in.
// src/routes/_authenticated.tsx
import { createFileRoute, redirect, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated')({
beforeLoad: ({ context, location }) => {
if (!context.auth.isAuthenticated) {
throw redirect({
to: '/login',
search: { redirect: location.href },
})
}
},
component: () => <Outlet />,
})
The beforeLoad runs before the child routes load. The unauthenticated user is redirected to the login. The location.href is the current URL, which the login page can use to redirect back after the successful login. The component: () => <Outlet /> is the pass-through: the layout has no visual UI, only the guard.
Complete Example Session
# ============================================
# PART 1: NESTED LAYOUT
# ============================================
// src/routes/dashboard.tsx
export const Route = createFileRoute('/dashboard')({
component: () => (
<div>
<Sidebar />
<Outlet />
</div>
),
})
# ============================================
# PART 2: CHILD ROUTE
# ============================================
// src/routes/dashboard.settings.tsx
export const Route = createFileRoute('/dashboard/settings')({
component: () => <h1>Settings</h1>,
})
# ============================================
# PART 3: PATHLESS LAYOUT
# ============================================
// src/routes/_app.tsx
export const Route = createFileRoute('/_app')({
component: () => (
<div>
<Header />
<Outlet />
</div>
),
})
# ============================================
# PART 4: CHILD OF PATHLESS
# ============================================
// src/routes/_app.settings.tsx
export const Route = createFileRoute('/_app/settings')({
component: () => <h1>Settings</h1>,
})
// URL is /settings, not /_app/settings
# ============================================
# PART 5: OUTLET CONTEXT
# ============================================
<Outlet context={{ user, setUser }} />
# ============================================
# PART 6: USE OUTLET CONTEXT
# ============================================
const { user } = useOutletContext()
# ============================================
# PART 7: AUTH GUARD
# ============================================
export const Route = createFileRoute('/_authenticated')({
beforeLoad: ({ context, location }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: '/login', search: { redirect: location.href } })
}
},
component: () => <Outlet />,
})
# ============================================
# PART 8: MULTIPLE LAYOUTS
# ============================================
__root → _app → _authenticated → dashboard.settings
# ============================================
# PART 9: VERIFY
# ============================================
# /settings → AppLayout + Settings
# /dashboard/settings → DashboardLayout + Settings
# Unauthenticated → redirect to /login
The nine parts trace the layout routes: nested, child, pathless, child pathless, context, use context, auth guard, multiple layouts, verification.
Quick Reference
Layout Types
| Type | File Pattern | URL |
|---|---|---|
| Nested | dashboard.tsx | /dashboard/... |
| Pathless | _app.tsx | /... (no _app) |
| Root | __root.tsx | Root of everything |
Hooks
| Hook | Returns |
|---|---|
useOutletContext() | The layout context |
Route.useRouteContext() | The route context |
useMatch() | The current match |
Redirect
| Function | Purpose |
|---|---|
redirect({ to, search }) | Navigate from beforeLoad |
Concepts
| Concept | Purpose |
|---|---|
<Outlet /> | Child route placeholder |
beforeLoad | Pre-render guard |
context prop | Pass data to children |
Best Practices
✅ Do This:
<Outlet />
<!-- Render the Outlet in the layout --> # ✅
export const Route = createFileRoute('/_app')({...})
<!-- Use the underscore for the pathless layout --> # ✅
const { user } = useOutletContext()
<!-- Use the context from the parent --> # ✅
❌ Don’t Do This:
# Forget the Outlet
<!-- The children never render --> # ❌
# Use the leading slash in the pathless layout
<!-- The layout becomes a URL segment --> # ❌
# Put the guard in the component instead of beforeLoad
<!-- The child may load before the guard --> # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Children not rendering | Missing <Outlet /> | Add the Outlet |
_app in URL | Wrong path in createFileRoute | Use /_app and the underscore |
| Guard not redirecting | No beforeLoad | Move the check to beforeLoad |
| Context undefined | No context prop on <Outlet /> | Pass the context |
| Nested layout broken | Wrong file naming | Verify the . separators |
Real-World Examples
1. Basic Layout
function Layout() { return <div><Sidebar /><Outlet /></div>; }
2. Pathless Layout
createFileRoute('/_app')({ component: AppLayout })
3. Child Route
createFileRoute('/_app/settings')({ component: Settings })
4. Outlet Context
<Outlet context={{ user }} />
5. Use Outlet Context
const { user } = useOutletContext()
6. Auth Guard
beforeLoad: ({ context }) => {
if (!context.auth) throw redirect({ to: '/login' })
}
7. Redirect with Search
throw redirect({ to: '/login', search: { redirect: location.href } })
8. Multiple Layouts
__root → _app → _authenticated → dashboard.settings
9. Nested Sidebar
function DashboardLayout() { return <div><Sidebar /><Outlet /></div>; }
10. Parent Route
createFileRoute('/dashboard')({ component: DashboardLayout })
Visual
Layout Hierarchy
┌──────────────────────────────────────────────┐
│ __root.tsx (root layout) │
│ ├── <Header /> │
│ ├── <Outlet /> │
│ │ ├── _app.tsx (pathless) │
│ │ │ ├── <Sidebar /> │
│ │ │ ├── <Outlet /> │
│ │ │ │ └── _app.settings.tsx │
│ │ └── _authenticated.tsx (guard) │
│ │ └── <Outlet /> │
│ │ └── dashboard.settings.tsx │
│ └── <Footer /> │
└──────────────────────────────────────────────┘
URL Structure
┌──────────────────────────────────────────────┐
│ URL: /settings │
│ Matches: __root + _app + settings │
│ → Sidebar and Header, no /_app in the URL │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ URL: /dashboard/settings │
│ Matches: __root + _authenticated + │
│ dashboard + settings │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Layout route | Parent route with <Outlet /> |
| Pathless layout | _name.tsx |
| Nested layout | name.tsx + child routes |
| Child placeholder | <Outlet /> |
| Context | <Outlet context={...} /> |
| Context read | useOutletContext() |
| Guard | beforeLoad with redirect |
| Root layout | __root.tsx |
Key takeaways:
- The layout route is the shared UI wrapper. The parent component renders the navigation, the sidebar, and the
<Outlet />. The child routes render inside the<Outlet />. Without the<Outlet />, the child routes do not render. - The pathless layout does not appear in the URL. The file name with the leading underscore (
_app.tsx) marks the route as the layout. The child URL is/settings, not/_app/settings. - The
<Outlet context={...} />passes the data. The child reads the data with theuseOutletContext(). The context is the alternative to the prop drilling. - The layout can be the guard. The
beforeLoadruns before the child loaders. The unauthenticated user is redirected to the login. Thelocation.hrefin the search enables the post-login redirect. - The layout hierarchy is the composition. The root wraps the pathless, the pathless wraps the section, and the section wraps the page. The URL segments match the path segments, except for the pathless layouts.
Remember: The layout is the parent. The <Outlet /> is the slot. The _name is the pathless. The context is the data. The beforeLoad is the guard. The redirect is the response. The exam tests the pathless layout and the <Outlet />.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!