| | |

TanStack Router 02 ⚛️ Root Route — Creating the root layout

⏱️ Estimated read: 11 min | |

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

OptionPurpose
componentLayout component
errorComponentError fallback
notFoundComponent404 fallback
loaderRoot data loading
beforeLoadPre-loading hook
pendingComponentLoading state

createRootRouteWithContext

FeaturePurpose
Context typeShared dependencies
useRouteContextAccess the context
from: '__root__'Root context

Hooks

HookReturns
useRouteContextRoute context
useMatchRouteMatch check
useNavigateNavigation function

Layout Best Practices

ElementPurpose
<Outlet />Child route placeholder
<nav>Global navigation
ProvidersTheme, 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

PitfallWhy It HappensFix
Blank pageMissing <Outlet />Add Outlet to layout
Error crashes appNo errorComponentAdd root error
404 not renderingNo notFoundComponentAdd root not-found
Context undefinedNo context passed to routerAdd context to createRouter
Type errors on contextMissing generic typeAdd <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

ItemValue
Root route functioncreateRootRoute
Context variantcreateRootRouteWithContext
Layout componentcomponent option
Child placeholder<Outlet />
Error fallbackerrorComponent
Not-found fallbacknotFoundComponent
Context accessuseRouteContext
Root route path'/'

Key takeaways:

  • The root route is the layout for the entire application. The component renders 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 errorComponent catches errors from any child route. The notFoundComponent renders when the URL does not match. Both should include a <Link to="/"> for recovery.
  • The createRootRouteWithContext variant is for shared dependencies. It requires a context type and allows the child routes to access the context with useRouteContext({ from: '__root__' }). The context is passed to the router with createRouter({ 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 />.

Was this helpful?


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!