Search parameters automatically inherit from parent routes in TanStack Router. When a parent route validates search parameters, child routes can access them via Route.useSearch() alongside their own parameters.
TanStack Router automatically merges search parameters from parent routes with child route parameters. This happens through the route hierarchy:
Share parameters across your entire application by validating them in the root route:
// routes/__root.tsx
import { createRootRoute, Outlet } from '@tanstack/react-router'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const globalSearchSchema = z.object({
theme: z.enum(['light', 'dark']).default('light'),
lang: z.enum(['en', 'es', 'fr']).default('en'),
debug: z.boolean().default(false),
})
export const Route = createRootRoute({
validateSearch: zodValidator(globalSearchSchema),
component: RootComponent,
})
function RootComponent() {
const { theme, lang, debug } = Route.useSearch()
return (
<div className={`app theme-${theme} lang-${lang}`}>
{debug && <DebugPanel />}
<Outlet />
</div>
)
}
// routes/products/index.tsx
import { createFileRoute } from '@tanstack/react-router'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const productSearchSchema = z.object({
page: z.number().default(1),
category: z.string().default('all'),
})
export const Route = createFileRoute('/products/')({
validateSearch: zodValidator(productSearchSchema),
component: ProductsPage,
})
function ProductsPage() {
// Contains both local (page, category) AND inherited (theme, lang, debug) parameters
const search = Route.useSearch()
return (
<div>
<h1>Products (Theme: {search.theme})</h1>
<p>Page: {search.page}</p>
<p>Category: {search.category}</p>
</div>
)
}
Share parameters within a section of your app using layout routes:
// routes/_authenticated.tsx
import { createFileRoute, Outlet } from '@tanstack/react-router'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const authSearchSchema = z.object({
impersonate: z.string().optional(),
sidebar: z.boolean().default(true),
notifications: z.boolean().default(true),
})
export const Route = createFileRoute('/_authenticated')({
validateSearch: zodValidator(authSearchSchema),
component: AuthenticatedLayout,
})
function AuthenticatedLayout() {
const search = Route.useSearch()
return (
<div className="authenticated-layout">
{search.sidebar && <Sidebar />}
<main className="main-content">
{search.notifications && <NotificationBar />}
<Outlet />
</main>
{search.impersonate && <ImpersonationBanner user={search.impersonate} />}
</div>
)
}
// routes/_authenticated/dashboard.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated/dashboard')({
component: DashboardPage,
})
function DashboardPage() {
// Contains inherited auth parameters (impersonate, sidebar, notifications)
const search = Route.useSearch()
return (
<div>
<h1>Dashboard</h1>
{search.impersonate && (
<Alert>Currently impersonating: {search.impersonate}</Alert>
)}
<DashboardContent />
</div>
)
}
Global Application Settings:
Section-Specific State:
Persistent UI State:
Cause: Parent route not validating the shared parameters.
// ❌ Root route missing validateSearch
export const Route = createRootRoute({
component: RootComponent, // No validateSearch
})
// Child route can't access theme parameter
function ProductsPage() {
const search = Route.useSearch() // No theme available
}
Solution: Add validateSearch to the parent route:
// ✅ Root route validates shared parameters
export const Route = createRootRoute({
validateSearch: zodValidator(globalSearchSchema),
component: RootComponent,
})
Cause: Not preserving inherited parameters during navigation.
// ❌ Navigation overwrites all search parameters
router.navigate({
to: '/products',
search: { page: 1 }, // Loses theme, lang, etc.
})
Solution: Preserve existing parameters with function syntax:
// ✅ Preserve existing parameters
router.navigate({
to: '/products',
search: (prev) => ({ ...prev, page: 1 }),
})
Cause: Child route schema doesn't account for inherited parameters.
// ❌ TypeScript error: Property 'theme' doesn't exist
const search = Route.useSearch()
console.log(search.theme) // Type error
Solution: TypeScript automatically infers inherited types when using validateSearch. No additional typing needed - the inheritance works automatically.