TanStack/router
github.com/TanStack/router ↗ · 4000 files · 10.5 MB · scan capped
TanStack Router A modern router designed for type safety, data‑driven navigation, and seamless developer experience.
Overview
TanStack Router is a TypeScript routing library with shared navigation and data-loading machinery plus React, Solid, and Vue bindings. This repository also implements TanStack Start, which adds request middleware, server functions, server routes, and streamed rendering. Its distinctive boundaries are typed route trees, separate client/server loading paths, request-scoped server state, and build-time route generation. This brief examines commit 1f0f20a3206a28365d74fd2485b9a8eedbf74dd0 through bounded source inspection; tests and CI were inspected, not executed.
System map
The repository publishes routing and full-stack framework packages rather than one deployable application. RouterCore owns shared routing, while framework bindings provide rendering and store integration. Start introduces HTTP request handling and request-local context on top of that core. Workspace scripts and CI coordinate builds, correctness checks, performance scenarios, and releases.
- React, Solid, and Vue expose createRouter factories whose Router classes extend RouterCore and pass getStoreFactory to its constructor.
- RouterCore.load dispatches to loadServerRoute on the server and otherwise updates the latest location before calling loadClientRoute.
- React Router uses nonreactive core stores on the server and React Store atoms with batching on the client.
- createStartHandler returns a RequestHandler that distinguishes server-function requests using SERVER_FN_BASE and supplies separate RPC and router terminals.
- Start stores request context in AsyncLocalStorage, while session helpers delegate to h3 against the current request event; these inspected layers expose request/session state rather than a database implementation.
- Router core publishes ESM and CommonJS entrypoints plus separate SSR client/server and conditional isServer exports; Start server core publishes ESM entrypoints and requires Node >=22.12.0.
- The pnpm@11.21.0 workspace includes packages, benchmarks, framework examples, and framework e2e projects; root test:pr uses nx affected and test:ci uses nx run-many across lint, unit, e2e, types, build checks, and builds.
- PR CI runs affected checks and publishes package previews; release CI builds packages before Changesets versioning/publishing and selects prerelease, maintenance, or latest behavior from branch names.
Feature catalog
The inspected public surface covers typed route construction, navigation, validated URL state, route loading, and framework presentation. Start adds server-function RPC, HTTP method handlers, middleware, and streaming SSR. Developer tooling generates route trees through a CLI or bundler plugin. The catalog describes capabilities demonstrated by inspected implementations and fixtures, not an exhaustive inventory of every package export.
- File routing supports React/Solid/Vue targets, physical or virtual route configuration, ignore rules, configurable index/route tokens, generated type suppression, and automatic code splitting.
- Typed route APIs connect useParams, useSearch, useRouteContext, useLoaderData, and useNavigate to route-specific parameter, context, and loader types.
- Link preloading supports intent, render, and viewport triggers; viewport mode uses IntersectionObserver with a 100px root margin and cleans up observers and pending timers.
- RouterProvider supplies context and current matches; Matches chooses server/direct presentation or client Suspense and supports an InnerWrap and a configurable global catch boundary.
- Navigation supports full document reloads, client location commits, masked browser URLs, replacement history entries, and optional browser view transitions.
- Start server functions accept serialized GET payloads, JSON POST bodies, and FormData; returned Responses can bypass RPC serialization, while other results use serializeResult.
- Exact server-route matches support method-specific and ANY handlers; HEAD dispatch prioritizes HEAD, then GET, then ANY, and routes with components can defer into rendering.
- The tsr CLI offers generate and watch; the Query SSR integration adds initial dehydrated queries and a query stream, while the Zod adapter exposes typed validation and fallback helpers.
Data flow
URLs become parsed locations, inherited validated search values, loader dependencies, and route matches. Client loading carries context and cancellation ownership through reusable resource work into framework presentation. Server requests supply their own Router, middleware context, and serialized transport. Durable outputs in the inspected paths are browser history, generated route files, and session operations delegated to h3.
- defaultParseSearch decodes the query string and attempts JSON parsing of eligible string values, retaining strings on parse failure; stringifySearchWith serializes complex values and preserves parseable strings through symmetric encoding.
- matchRoutesInternal validates search parent-to-child, merges strict search values, records SearchParamError on failure unless throwOnError is set, and computes loaderDeps plus their JSON hash before match creation.
- Client contextualize starts with parent or router context, adds cached route-context contributions, invokes beforeLoad with validated search and additionalContext, and merges its successful result after checking cancellation.
- loadResource creates loader work with a separate AbortController and normalizes outcomes; shared-flight registry membership and leases determine discovery and retention of failed work.
- commitLocation places masked locations and hash-scroll metadata into history state, then pushes or replaces publicHref; when no adapter subscribes, it triggers loading itself.
- Start memoizes its Router within one request, installs createServerHistory(href), request origin and serialization adapters, then passes serverContext through additionalContext before loading.
- RPC puts GET data/context into a serialized payload query parameter and POST data into FormData or JSON; the server deserializes with Seroval and merges client context before trusted server context.
- Generated tree content is compared with the cached file before writing; safeFileWrite writes a temporary file, checks the target strategy, renames it, and caches the target's post-rename timestamp.
Behavior traces
The investigation followed browser startup, client navigation, server rendering, RPC, and generated-file output through their concrete functions. Each trace retains meaningful branches such as canonical redirects, document navigation, method mismatch, or unchanged generated output. Authentication persistence was also traced through a browser-test fixture and the session wrapper. These are source-derived execution paths, not observed runtime runs.
- Browser startup: main.tsx imports routeTree → createRouter → Router constructor → RouterCore.update → ReactDOM.createRoot → RouterProvider → Matches → client Transitioner; rendering occurs only when #app exists and has empty innerHTML.
- Initial client load: Transitioner subscribes history to router.load → updateLatestLocation → buildLocation with search validation → replace/ignoreBlocker if the URL is noncanonical; otherwise it loads synchronously unless already resolved or a transaction exists.
- Primary link action: handleClick intercepts only an enabled, unmodified, unprevented left click targeting self → router.navigate → buildAndCommitLocation → commitLocation → history push/replace → subscribed router.load → loadClientRoute; absolute href or reloadDocument takes documentNavigation.
- Client route work: loadClientRoute matches routes and acquires resources → creates a LoadTransaction → runClientTransaction; contextualize establishes inherited context and beforeLoad, while loadResource executes normalized loader work.
- HTML request: createStartHandler normalizes the URL → global middleware → handleServerRoutes → executeRouter → attachRouterServerSsrUtils → router.load → serverSsr.dehydrate → renderer callback; malformed protocol-relative paths redirect with 308 and incompatible Accept headers return 406.
- React streaming: defaultStreamHandler → renderRouterToStream → React readable-stream renderer when available, otherwise pipeable renderer → transformReadableStreamWithRouter → createSsrStreamResponse; bot detection waits for full readiness rather than ordinary shell readiness.
- Server RPC: serverFnFetcher serializes and fetches → createStartHandler extracts serverFnId → handleServerAction resolves getServerFnById with client origin → rejects method mismatch before decoding → invokes action → returns a raw Response or serialized result.
- Output and persistence paths: Vite configResolved → initConfigAndGenerator → generate → Generator.run → generatorInternal → physical/virtual discovery → safeFileWrite; separately, the auth fixture's loginFn validates fixed test credentials → useSession → session.update → redirect, and logoutFn clears the session before redirecting.
Testing signals
Tests and CI were inspected, not executed; no passing result, coverage percentage, or benchmark outcome is asserted. The inspected suites combine Vitest, React Testing Library, type assertions, generator snapshots, and Playwright browser checks. CI definitions connect affected targets and framework benchmark matrices. The inventory and test inspection are bounded, and mocked unit boundaries do not establish deployment-level correctness.
- Router core config uses Vitest in jsdom, disables watching, enables type checking, and aliases isServer to its development implementation so tests can exercise router.isServer fallback.
- The server-route lifecycle regression asserts beforeLoad → loader → onEnter order, onStay after a search change, and onLeave before entry into a different route.
- React render-owner tests use React Testing Library and controlled suspension to assert that a superseded publication cannot acknowledge its successor or emit an obsolete onRendered event.
- routeApi.test-d.tsx uses Vitest expectTypeOf for route-specific navigation, params, context, search, and loader return types; Start server core also declares type checks across TypeScript 5.6 through 7.0 and export checks using publint/attw.
- Generator tests enumerate fixture folders, compare generated route trees and selected transformed route files with file snapshots, and inspect absolute physical mount directories; expected-error branches catch errors without an explicit failure assertion when no error is thrown.
- Server-function tests cover direct falsy results, JSON records, response cancellation, and request-abort cleanup, but mock the function resolver, response event, and plugin lookup; those cases do not exercise the real dispatch or middleware pipeline.
- The inspected Playwright router config builds and previews an app with one Chromium worker; browser specs assert same-origin behavior for control-character URLs and exercise auth login/logout, direct authorization, reload persistence, and disabled prehydration credential controls.
- PR CI invokes test:pr; CodSpeed CI defines client-nav, SSR, and memory workloads across React/Solid/Vue; bundle tooling declares Node test-runner checks, while router backpressure performance tests are excluded unless RUN_BACKPRESSURE_PERF=1.
Risk map
The main sensitive boundaries are URL interpretation, RPC decoding, middleware trust, SSR stream ownership, and generator filesystem access. Several protections are explicit, but their effectiveness has not been dynamically verified here. The risks below are source observations or labeled hypotheses, not confirmed vulnerabilities. Test limitations are specific to the inspected files and do not imply whole-repository absence of coverage.
- CSRF configuration changes when an application supplies a Start instance: the implicit default middleware is used only without that instance, and a missing custom CSRF middleware produces a nonproduction warning rather than automatic installation.
- CSRF validation prioritizes Sec-Fetch-Site, then Origin, then Referer; requests lacking all applicable checks are denied unless allowRequestsWithoutOriginCheck is explicitly true.
- RPC checks the declared HTTP method before parsing and applies an explicit size limit to GET payload strings; the inspected POST JSON and FormData branches invoke request parsers without that same local size check.
- Client-supplied RPC context crosses a trust boundary in GET, JSON, and FormData; safeObjectMerge places server context last, and repository instructions require preserving that precedence.
- Redirect handling rejects protocol-relative/control-character variants and schemes outside protocolAllowlist, while intentionally allowing external HTTP(S) redirects; same-origin enforcement is not a blanket redirect policy.
- SSR transport recognizes fixed byte patterns rather than parsing HTML; its documented contract requires Scripts in body and forbids an earlier exact </body></html> in raw data after the hydration boundary.
- Generator routesDirectory, generatedRouteTree, and tmpDir can resolve to absolute paths outside the project; safeFileWrite performs writes, chmod/chown, and rename at those configured locations.
- Hypothesis: a generator regression that stops throwing for an invalid fixture could escape the inspected expected-error branch, because its assertions execute only inside catch and it does not assert rejection or fail after normal completion. (inferred)
Extension points
Extension is concentrated in route options, framework factories, generator hooks, middleware, and transport adapters. These hooks allow application behavior without moving request state into shared metadata. Build integration is split between generator configuration and bundler lifecycle hooks. SSR extensions must preserve the existing cleanup and serialization ownership contracts.
- Framework integration plugs into RouterCore through the constructor's GetStoreConfig argument; the React factory chooses mutable/readonly implementations and batching by runtime.
- Route hooks extend the loading pipeline through context, beforeLoad, and loader; beforeLoad receives params, search, navigation helpers, abortController, cause, and additionalContext.
- Applications can replace search serialization through parseSearch/stringifySearch options or the parseSearchWith/stringifySearchWith factories.
- React presentation extends through RouterOptionsExtensions defaults, Wrap/InnerWrap, and RouterContextProvider's merged runtime context/options.
- GeneratorPlugin provides named init, afterTransform, and onRouteTreeChanged hooks with generator or route-node/tree information.
- createRouterGeneratorPlugin accepts configuration or a configuration factory, supports disabling generation, and connects Vite configResolved plus bundler watch events to Generator.run.
- Start combines application and plugin serialization adapters, exposes global request middleware, and allows route-method middleware plus handler functions or handler objects.
- SSR integrates through createStartHandler's callback/options form and Router dehydrate/lifecycle hooks; Query integration chains existing dehydration and registers cleanup immediately when server SSR attaches.
Contributor handoff
Begin at the affected core boundary and its framework consumers, then read the local instructions and executable configuration as source. Tests and CI were inspected, not executed; all commands here describe future validation when execution is authorized. This brief is a bounded investigation of the pinned commit, not comprehensive coverage or a vulnerability audit. Preserve public contracts, request isolation, cancellation ownership, and generated-file boundaries.
- Read root and applicable nested AGENTS.md first; shared routing changes belong in router-core and require checking React, Solid, and Vue consumers, while shared Start runtime/build changes belong in start-*-core.
- Begin bug fixes with reachable public-API regressions; verify upstream dependency root causes and obtain explicit authorization before implementing a repository workaround.
- Use the pinned pnpm version and repository Node selection, preserve the lockfile and generated route trees, and use control-flow braces; format before authorized validation.
- Use one Nx command at a time with CI=1 NX_DAEMON=false and --skipRemoteCache; workspace imports consume built packages, and unit targets depend on upstream builds.
- Future code validation should include affected unit, type, and lint targets plus relevant consumers; browser behavior needs e2e, exports need build checks, and published-code changes need a changeset. Their definitions were inspected here, not executed.
- Keep isServer ?? router.isServer directly in runtime branches and avoid server reactivity; shared definitions must not acquire request/user data, and development caching/HMR guards must remain intact.
- Before SSR or RPC transport edits, read STREAMING.md and Start server instructions; preserve response-body ownership, trusted context precedence, and frame compatibility between server and client.
- Backpressure validation requires RUN_BACKPRESSURE_PERF=1 and cache bypass; runtime/memory changes require paired identical workloads, and every emitted-client-JavaScript change requires the full final bundle-size skill workflow.
Languages
| Language | Files | Share of source |
|---|---|---|
| TypeScript | 8780 | 96% |
| JSON | 1338 | docs/config |
| Markdown | 527 | docs/config |
| JavaScript | 290 | 3% |
| CSS | 279 | docs/config |
| HTML | 127 | docs/config |
| Vue | 63 | 1% |
| YAML | 39 | docs/config |
Start reading here
Probable source entrypoint: packages/ — inferred from the directory tree and manifest, not the upstream docs.
Source roots
packages/ (918 files)
Repository structure
| Top-level path | Files |
|---|---|
benchmarks/ | 919 |
e2e/ | 919 |
packages/ | 918 |
examples/ | 918 |
docs/ | 248 |
scripts/ | 27 |
.github/ | 14 |
_artifacts/ | 5 |
Docs found: AGENTS.mdCONTRIBUTING.mdREADME.mddocs/AGENTS.mddocs/router/api/file-based-routing.mddocs/router/api/router.mddocs/router/api/router/ActiveLinkOptionsType.mddocs/router/api/router/AsyncRouteComponentType.mddocs/router/api/router/FileRouteClass.mddocs/router/api/router/LinkOptionsType.md
Package
package.json — root, 20 declared dependencies.
@changesets/changelog-github@changesets/cli@nx/devkit@playwright/test@swc-node/register@tanstack/eslint-config@types/node@typescript/nativeeslinteslint-plugin-unused-importsmarkdown-link-extractornx
CI & config entrypoints
README.md— Repository readmeAGENTS.md— Agent instructions.github/workflows/autofix.yml— GitHub Actions workflow.github/workflows/bundle-size.yml— GitHub Actions workflow.github/workflows/check-skills.yml— GitHub Actions workflow.github/workflows/client-nav-benchmarks.yml— GitHub Actions workflow.github/workflows/labeler.yml— GitHub Actions workflow.github/workflows/pr.yml— GitHub Actions workflow.github/workflows/release.yml— GitHub Actions workflow.github/workflows/zizmor.yml— GitHub Actions workflow
Structural health signals
Average score 7.6/10 across 96 analyzed source files; 10 hotspots surfaced.
examples/react/start-basic-auth/src/prisma-generated/models/User.ts— 1323 lines, score 5 (Large file, Deep nesting)benchmarks/client-nav/link-performance/src/workload.tsx— 506 lines, score 5.5 (Large file, Deep nesting)examples/react/kitchen-sink-react-query/src/main.tsx— 1123 lines, score 5.7 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
1f0f20a3206a2026-10-01 — chore(react-start-rsc): upgrade Rspack to 2.2.7 (#8542)d35aab425b072026-10-01 — fix(router): cancel pending link preload before navigation (#8579)57e126e99dbd2026-09-30 — ci: Version Packages (#8531)d521abd71c692026-09-30 — fix(start): align server function transport behavior (#8573)614bc27034c82026-09-30 — chore(deps): update TanStack Store to 0.11.2 (#8568)