executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
prefix (`$postId`) marks a dynamic path segment, matching `Route.useParams()`.\n- `index.tsx` inside a folder matches the folder's own path with no additional segment.\n\n## Route Definition\n\n```tsx\nexport const Route = createFileRoute('/posts/$postId')({\n loader: async ({ params }) => fetchPost(params.postId),\n component: PostComponent,\n errorComponent: ({ error }) => \u003cErrorBanner message={error.message} />,\n pendingComponent: () => \u003cPostSkeleton />,\n})\n\nfunction PostComponent() {\n const post = Route.useLoaderData() // type-safe\n const { postId } = Route.useParams() // type-safe\n return \u003cdiv>{post.title}\u003c/div>\n}\n```\n\n- `errorComponent` renders when the loader throws; `pendingComponent` renders while the loader is in flight (subject to `defaultPendingMs`).\n- Loader return values and thrown errors are both fully typed and flow into `useLoaderData()` and `errorComponent` respectively.\n\n## Type-Safe Search Params\n\n- Always define search params with Zod and `validateSearch`.\n- Access them with `Route.useSearch()` — never read `window.location.search` or `URLSearchParams` directly, which bypasses type safety and validation.\n\n```tsx\nconst searchSchema = z.object({\n page: z.number().int().min(1).default(1),\n q: z.string().optional(),\n})\n\nexport const Route = createFileRoute('/search')({\n validateSearch: searchSchema,\n component: SearchPage,\n})\n\nfunction SearchPage() {\n const { page, q } = Route.useSearch()\n const navigate = Route.useNavigate()\n\n return (\n \u003cbutton onClick={() => navigate({ search: (prev) => ({ ...prev, page: page + 1 }) })}>\n Next page\n \u003c/button>\n )\n}\n```\n\n## Navigation\n\n- Use `\u003cLink>` for internal navigation — never a raw `\u003ca href>`, which triggers a full page reload and loses client-side routing state.\n- Always pass typed `params` and `search`; the TypeScript compiler catches missing or mistyped route params at build time.\n\n```tsx\n\u003cLink to=\"/posts/$postId\" params={{ postId: '123' }}>View Post\u003c/Link>\n\u003cLink to=\"/search\" search={{ page: 1, q: 'react' }}>Search\u003c/Link>\n```\n\n## Loaders + TanStack Query Integration\n\n- Put the `QueryClient` in router context so loaders can prefetch and cache through TanStack Query rather than duplicating fetch logic.\n- Use `ensureQueryData` (not `fetchQuery`) in loaders so an already-cached query is reused instead of refetched.\n\n```tsx\nexport const Route = createFileRoute('/posts')({\n loader: ({ context: { queryClient } }) =>\n queryClient.ensureQueryData(postsQueryOptions()),\n component: PostsPage,\n})\n\nfunction PostsPage() {\n const { data: posts } = useSuspenseQuery(postsQueryOptions())\n return \u003cPostList posts={posts} />\n}\n```\n\n## Router Context for Dependency Injection\n\n```tsx\n// __root.tsx\ninterface RouterContext {\n queryClient: QueryClient\n auth: AuthState\n}\n\nexport const Route = createRootRouteWithContext\u003cRouterContext>()({\n component: RootLayout,\n})\n\n// main.tsx\nconst router = createRouter({ routeTree, context: { queryClient, auth } })\n```\n\n- Router context flows down to every route's `loader` and `beforeLoad` via the `context` argument, giving each route typed access to shared dependencies without prop drilling or global singletons.\n\n## Auth Guards\n\n```tsx\nexport const Route = createFileRoute('/_auth/dashboard')({\n beforeLoad: ({ context }) => {\n if (!context.auth.isAuthenticated) throw redirect({ to: '/login' })\n },\n component: Dashboard,\n})\n```\n\n- `beforeLoad` runs before the loader and before the component renders, making it the right place for auth checks, feature flag gates, and redirects.\n- Prefer `throw redirect(...)` over imperative navigation inside components — it works during SSR, preloading, and client navigation alike.\n\n## Performance\n\n- Set `defaultPreload: 'intent'` on the router so links prefetch their route's data on hover/focus, making navigation feel instant.\n- Use `React.lazy` (or the router's built-in code-splitting via `.lazy()` route files) for route component code splitting on large apps.\n- Install `@tanstack/router-devtools` and render `\u003cTanStackRouterDevtools />` in development to inspect route matches, pending states, and cache.\n\n## Common Mistakes\n\n- Fetching data with `useEffect` inside a route component instead of a `loader`, which loses preloading and creates render-then-fetch waterfalls.\n- Reading raw `window.location.search` instead of `Route.useSearch()`, bypassing the search param schema and type safety.\n- Performing auth checks only inside components (`useEffect` redirect) instead of `beforeLoad`, allowing a flash of protected content before redirecting.\n- Forgetting to add new route params/search fields to `\u003cLink>` call sites — let the compiler catch it rather than testing manually.\n","licenseDocuments":[]}}
tanstack-routerType-safe, file-based routing for React with TanStack Router. Use when defining routes with createFileRoute, validating search params with Zod, writing route loaders, setting up auth guards with beforeLoad, integrating TanStack Query into loaders, or configuring router context and preloading.
Independent GitHub snapshot · Automated classification · Original documentation governs
TanStack Router is a fully type-safe router for React that treats routes, loaders, params, and search params as first-class typed data rather than untyped strings.
src/routes/ following the file-based routing convention (see below).Route from createFileRoute('/path')({...}) with component, and optionally loader, validateSearch, beforeLoad, errorComponent, and pendingComponent.validateSearch.loader, not in a component useEffect; integrate with TanStack Query via queryClient.ensureQueryData when caching is needed.beforeLoad checks (e.g. auth) that throw redirect({ to: '/login' }) when a precondition fails.<Link to="/path" params={...} search={...}> so the compiler validates params and search at every call site.routeTree.gen.ts is regenerated (automatic under the Vite plugin's dev server / build) before running or building the app.@tanstack/router-vite-plugin (or @tanstack/router-plugin/vite) for scalability over manually constructed route trees.createFileRoute (leaf/nested routes) or createRootRoute / createRootRouteWithContext (root).loader functions, not in component useEffect — this enables preloading, parallel loading, and pending/error states.src/routes/
__root.tsx ← Root layout
index.tsx ← / route
posts/
index.tsx ← /posts
$postId.tsx ← /posts/:postId (dynamic segment)
_layout.tsx ← Layout route (no path segment)
_auth/ ← Pathless auth layout group
dashboard.tsx
_layout, _auth) creates a pathless layout route used purely for grouping/shared UI.$ prefix ($postId) marks a dynamic path segment, matching Route.useParams().index.tsx inside a folder matches the folder's own path with no additional segment.export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => fetchPost(params.postId),
component: PostComponent,
errorComponent: ({ error }) => <ErrorBanner message={error.message} />,
pendingComponent: () => <PostSkeleton />,
})
function PostComponent() {
const post = Route.useLoaderData() // type-safe
const { postId } = Route.useParams() // type-safe
return <div>{post.title}</div>
}
errorComponent renders when the loader throws; pendingComponent renders while the loader is in flight (subject to defaultPendingMs).useLoaderData() and errorComponent respectively.validateSearch.Route.useSearch() — never read window.location.search or URLSearchParams directly, which bypasses type safety and validation.const searchSchema = z.object({
page: z.number().int().min(1).default(1),
q: z.string().optional(),
})
export const Route = createFileRoute('/search')({
validateSearch: searchSchema,
component: SearchPage,
})
function SearchPage() {
const { page, q } = Route.useSearch()
const navigate = Route.useNavigate()
return (
<button onClick={() => navigate({ search: (prev) => ({ ...prev, page: page + 1 }) })}>
Next page
</button>
)
}
<Link> for internal navigation — never a raw <a href>, which triggers a full page reload and loses client-side routing state.params and search; the TypeScript compiler catches missing or mistyped route params at build time.<Link to="/posts/$postId" params={{ postId: '123' }}>View Post</Link>
<Link to="/search" search={{ page: 1, q: 'react' }}>Search</Link>
QueryClient in router context so loaders can prefetch and cache through TanStack Query rather than duplicating fetch logic.ensureQueryData (not fetchQuery) in loaders so an already-cached query is reused instead of refetched.export const Route = createFileRoute('/posts')({
loader: ({ context: { queryClient } }) =>
queryClient.ensureQueryData(postsQueryOptions()),
component: PostsPage,
})
function PostsPage() {
const { data: posts } = useSuspenseQuery(postsQueryOptions())
return <PostList posts={posts} />
}
// __root.tsx
interface RouterContext {
queryClient: QueryClient
auth: AuthState
}
export const Route = createRootRouteWithContext<RouterContext>()({
component: RootLayout,
})
// main.tsx
const router = createRouter({ routeTree, context: { queryClient, auth } })
loader and beforeLoad via the context argument, giving each route typed access to shared dependencies without prop drilling or global singletons.export const Route = createFileRoute('/_auth/dashboard')({
beforeLoad: ({ context }) => {
if (!context.auth.isAuthenticated) throw redirect({ to: '/login' })
},
component: Dashboard,
})
beforeLoad runs before the loader and before the component renders, making it the right place for auth checks, feature flag gates, and redirects.throw redirect(...) over imperative navigation inside components — it works during SSR, preloading, and client navigation alike.defaultPreload: 'intent' on the router so links prefetch their route's data on hover/focus, making navigation feel instant.React.lazy (or the router's built-in code-splitting via .lazy() route files) for route component code splitting on large apps.@tanstack/router-devtools and render <TanStackRouterDevtools /> in development to inspect route matches, pending states, and cache.useEffect inside a route component instead of a loader, which loses preloading and creates render-then-fetch waterfalls.window.location.search instead of Route.useSearch(), bypassing the search param schema and type safety.useEffect redirect) instead of beforeLoad, allowing a flash of protected content before redirecting.<Link> call sites — let the compiler catch it rather than testing manually.