TanStack Router 02 ⚛️ Root Route — Creating the root layout
The previous chapter installed TanStack Router and configured the Vite plugin. This chapter covers the root route, which is the foundation of the entire route tree. Every application has exactly one root route, created with createRootRoute or createRootRouteWithContext. The root route defines the layout that wraps every other route: the navigation header, the footer, the global providers, and the <Outlet /> where the child routes render. The LFCS exam does not test TanStack Router, but the front-end engineering certification paths do. The critical detail is the difference between createRootRoute and createRootRouteWithContext. The context variant allows the root route to require a typed context (such as an authentication state or a query client) that is available to every child route via the useRouteContext hook.
Key point: The root route is created with createRootRoute({ component, errorComponent, notFoundComponent }). The component renders the layout. The <Outlet /> inside the layout is where the child routes render. The errorComponent catches errors from any child route. The notFoundComponent renders when no route matches. The createRootRouteWithContext is the variant for applications that need to pass a typed context to all routes.
Why the root route requires a specific mental model
The layout problem. The root route is the layout. Every other route renders inside it. The navigation header appears on every page because the root route’s component renders it. The footer appears on every page for the same reason. The <Outlet /> is the placeholder: the router replaces it with the matched child route’s component. Without the <Outlet />, the child routes never render, and the application shows only the layout with no content.
The global provider problem. The root route’s component is the correct place for the global providers: the theme provider, the authentication provider, the query client provider, and the devtools. These providers wrap the <Outlet />, so every child route has access to them. Placing a provider in a child route would limit its scope to that route. The root route is the only location that wraps everything.
The error handling problem. The errorComponent in the root route catches errors from any child route. If a loader throws an error, or a component throws during rendering, the nearest errorComponent renders. Placing the error component at the root route means that the entire application has a fallback. The <Link to="/"> inside the error component gives the user a way to recover.
The not-found problem. The notFoundComponent renders when the URL does not match any route. It is the TanStack Router equivalent of the React Router wildcard route. The root route’s notFoundComponent is the global fallback. A specific route can define its own notFoundComponent for a more specific message, but the root route is the catch-all.
The context problem. The createRootRouteWithContext variant allows the root route to require a context. The context is the object passed to createRouter({ context: { ... } }). Every child route can access it with useRouteContext({ from: '__root__' }). The context is the mechanism for the dependency injection: the authentication state, the query client, the API client, the theme.
The trade-off. The createRootRoute is simpler and is the correct choice for a small application that does not need a typed context. The createRootRouteWithContext is more complex but provides the type safety for the shared dependencies. The exam tests both and the distinction between the component and the errorComponent.
a. Creating the root route with createRootRoute
The createRootRoute function creates the root route. The component property renders the layout.
// src/routes/__root.tsx
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
export const Route = createRootRoute({
component: RootLayout,
})
function RootLayout() {
return (
<>
<header>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
</header>
<main>
<Outlet />
</main>
<footer>© 2026 My App</footer>
</>
)
}
The RootLayout renders the header, the <Outlet />, and the footer. The <Outlet /> is where the matched child route renders. The <Link> components are the navigation. The header and footer appear on every page because the root route wraps every child .
The createRootRoute function accepts an options object. The component is the layout. The errorComponent is the error fallback. The notFoundComponent is the 404 fallback. The loader is the data loading function for the root route (if any). The beforeLoad is the pre-loading hook for the authentication and the redirects.
b. The error and not-found components
The errorComponent catches errors from the route and its children. The notFoundComponent renders when the URL does not match.
// src/routes/__root.tsx
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
export const Route = createRootRoute({
component: RootLayout,
errorComponent: RootErrorComponent,
notFoundComponent: NotFoundComponent,
})
function RootErrorComponent({ error }: { error: Error }) {
return (
<div>
<h1>Something went wrong</h1>
<p>{error.message}</p>
<Link to="/">Home</Link>
</div>
)
}
function NotFoundComponent() {
return (
<div>
<h1>404 — Not Found</h1>
<Link to="/">Home</Link>
</div>
)
}
The errorComponent receives the error prop, which is the error thrown by the route or its children. The notFoundComponent receives no props. Both render inside the <Outlet /> if the root route’s component is not replaced. If the root route’s component is replaced by the error component, the <Outlet /> is not rendered, and the error message is the entire page .
The notFoundComponent is the TanStack Router equivalent of the React Router wildcard route. It renders when the URL does not match any route. The Link to="/" gives the user a way to return to the home page.
c. The createRootRouteWithContext variant
The createRootRouteWithContext requires a context type. The context is passed to the router with createRouter({ context: { ... } }).
// src/routes/__root.tsx
import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'
interface RouterContext {
auth: AuthState
queryClient: QueryClient
}
export const Route = createRootRouteWithContext<RouterContext>()({
component: RootLayout,
})
function RootLayout() {
return <Outlet />
}
The RouterContext interface defines the context type. The createRootRouteWithContext<RouterContext>() creates the root route with the context. The createRouter({ context: { auth, queryClient } }) passes the actual context object.
The child routes access the context with useRouteContext.
// src/routes/dashboard.tsx
import { createFileRoute, useRouteContext } from '@tanstack/react-router'
export const Route = createFileRoute('/dashboard')({
component: Dashboard,
})
function Dashboard() {
const { auth } = useRouteContext({ from: '__root__' })
return <div>Welcome, {auth.user.name}</div>
}
The useRouteContext({ from: '__root__' }) returns the root route’s context. The context is type-safe: the auth property is typed as AuthState. The queryClient is also available, which is used with the query integration.
The createRootRouteWithContext is the correct choice for an application that has a shared authentication state and a query client. The context is the mechanism for the dependency injection. The createRootRoute is the simpler choice for an application that does not have shared dependencies.
Complete Example Session
# ============================================
# PART 1: CREATE THE ROOT ROUTE
# ============================================
// src/routes/__root.tsx
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
export const Route = createRootRoute({
component: RootLayout,
})
# ============================================
# PART 2: DEFINE THE LAYOUT
# ============================================
function RootLayout() {
return (
<>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<main><Outlet /></main>
</>
)
}
# ============================================
# PART 3: ADD THE ERROR COMPONENT
# ============================================
export const Route = createRootRoute({
component: RootLayout,
errorComponent: RootErrorComponent,
})
function RootErrorComponent({ error }: { error: Error }) {
return <div><h1>Error</h1><p>{error.message}</p></div>
}
# ============================================
# PART 4: ADD THE NOT FOUND COMPONENT
# ============================================
export const Route = createRootRoute({
component: RootLayout,
errorComponent: RootErrorComponent,
notFoundComponent: NotFoundComponent,
})
function NotFoundComponent() {
return <div><h1>404</h1><Link to="/">Home</Link></div>
}
# ============================================
# PART 5: USE createRootRouteWithContext
# ============================================
import { createRootRouteWithContext } from '@tanstack/react-router'
interface RouterContext {
auth: AuthState
}
export const Route = createRootRouteWithContext<RouterContext>()({
component: RootLayout,
})
# ============================================
# PART 6: PASS THE CONTEXT
# ============================================
const router = createRouter({
routeTree,
context: { auth },
})
# ============================================
# PART 7: ACCESS THE CONTEXT
# ============================================
import { useRouteContext } from '@tanstack/react-router'
function Dashboard() {
const { auth } = useRouteContext({ from: '__root__' })
return <div>Hello, {auth.user.name}</div>
}
# ============================================
# PART 8: ADD GLOBAL PROVIDERS
# ============================================
function RootLayout() {
return (
<ThemeProvider>
<AuthProvider>
<QueryClientProvider client={queryClient}>
<nav><Link to="/">Home</Link></nav>
<Outlet />
</QueryClientProvider>
</AuthProvider>
</ThemeProvider>
)
}
# ============================================
# PART 9: VERIFY THE LAYOUT
# ============================================
# / → Home (inside layout)
# /about → About (inside layout)
# /unknown → NotFound (inside layout)
# Error in loader → RootErrorComponent
The nine parts trace the root route: creation, layout, error component, not-found component, context variant, context passing, context access, global providers, and verification.
Quick Reference
createRootRoute Options
| Option | Purpose |
|---|---|
component | Layout component |
errorComponent | Error fallback |
notFoundComponent | 404 fallback |
loader | Root data loading |
beforeLoad | Pre-loading hook |
pendingComponent | Loading state |
createRootRouteWithContext
| Feature | Purpose |
|---|---|
| Context type | Shared dependencies |
useRouteContext | Access the context |
from: '__root__' | Root context |
Hooks
| Hook | Returns |
|---|---|
useRouteContext | Route context |
useMatchRoute | Match check |
useNavigate | Navigation function |
Layout Best Practices
| Element | Purpose |
|---|---|
<Outlet /> | Child route placeholder |
<nav> | Global navigation |
| Providers | Theme, auth, query |
<footer> | Global footer |
Best Practices
✅ Do This:
<Outlet />
<!-- Always include the Outlet in the root layout --> # ✅
errorComponent: RootErrorComponent
<!-- Add a root error fallback --> # ✅
notFoundComponent: NotFoundComponent
<!-- Add a root 404 fallback --> # ✅
❌ Don’t Do This:
# Forget the Outlet
<!-- Child routes never render --> # ❌
# Put global providers in child routes
<!-- Providers do not wrap the whole app --> # ❌
# Use createRootRouteWithContext without passing the context
<!-- Type errors on useRouteContext --> # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Blank page | Missing <Outlet /> | Add Outlet to layout |
| Error crashes app | No errorComponent | Add root error |
| 404 not rendering | No notFoundComponent | Add root not-found |
| Context undefined | No context passed to router | Add context to createRouter |
| Type errors on context | Missing generic type | Add <RouterContext> |
Real-World Examples
1. Basic Root
export const Route = createRootRoute({ component: RootLayout })
2. Root with Outlet
function RootLayout() { return <><nav /><Outlet /></>; }
3. Root Error
errorComponent: ({ error }) => <div>{error.message}</div>
4. Root Not Found
notFoundComponent: () => <div>404</div>
5. Root with Context
createRootRouteWithContext<RouterContext>()({ component: RootLayout })
6. Pass Context
createRouter({ routeTree, context: { auth } })
7. Use Context
const { auth } = useRouteContext({ from: '__root__' })
8. Global Provider
<QueryClientProvider client={queryClient}><Outlet /></QueryClientProvider>
9. Link in Error
<Link to="/">Home</Link>
10. Devtools
<TanStackRouterDevtools />
Visual
Root Route Structure
┌──────────────────────────────────────────────┐
│ RootLayout │
│ ├── <nav> (global navigation) │
│ ├── <Outlet /> │
│ │ ├── Index route │
│ │ ├── About route │
│ │ └── Other routes │
│ └── <footer> (global footer) │
└──────────────────────────────────────────────┘
Error Handling
┌──────────────────────────────────────────────┐
│ Route throws error │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Nearest errorComponent │
│ ├── Root route: global fallback │
│ └── Child route: specific fallback │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Root route function | createRootRoute |
| Context variant | createRootRouteWithContext |
| Layout component | component option |
| Child placeholder | <Outlet /> |
| Error fallback | errorComponent |
| Not-found fallback | notFoundComponent |
| Context access | useRouteContext |
| Root route path | '/' |
Key takeaways:
- The root route is the layout for the entire application. The
componentrenders the navigation, the footer, and the<Outlet />. The<Outlet />is where the child routes render. Without it, the application shows only the layout and no content . - The error and not-found components are the fallbacks. The
errorComponentcatches errors from any child route. ThenotFoundComponentrenders when the URL does not match. Both should include a<Link to="/">for recovery. - The
createRootRouteWithContextvariant is for shared dependencies. It requires a context type and allows the child routes to access the context withuseRouteContext({ from: '__root__' }). The context is passed to the router withcreateRouter({ context }). - The global providers belong in the root layout. The theme provider, the authentication provider, and the query client provider wrap the
<Outlet />. Placing them in a child route limits their scope. - The
<Outlet />is the connection. The root route renders the layout, and the<Outlet />is where the matched child route renders. The placement of the<Outlet />determines the layout structure.
Remember: The root route is the skeleton. The <Outlet /> is the hole where the child routes render. The error component is the failure fallback. The not-found component is the 404 fallback. The context is the dependency injection. The exam tests the distinction between createRootRoute and createRootRouteWithContext, and the placement of 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!