Capítulo 4 de 57

Chapter 4: Decisions on DX

Core Idea

TanStack Router's unusual developer experience decisions (route configuration boilerplate, module declaration for the router instance, and a preference for file-based routing) all exist to maximize TypeScript's inference capabilities without sacrificing control or maintainability, tracing back to its origin at Nozzle.io where type-safe URL search params were a hard requirement.

Key Concepts

  • Origin story: TanStack Router originated at Nozzle.io, which needed a client-side router with first-in-class URL search parameter handling without compromising type safety for complex dashboards.
  • No JSX route definitions: JSX-based route trees (<Route path="/posts" component={...} />) are rejected because TypeScript cannot infer route configuration types from them, which would break typesafe Link/navigate.
  • Nested object route trees rejected: Defining routes as one large nested object is easy to visualize but doesn't scale, is hard to code-split, and gets worse as loaders/context/search validation are added.
  • getParentRoute: Each route created via createRoute must supply a getParentRoute function returning its parent, so the child route can correctly infer parent context, path params, and search params; this is a common point of failure if done incorrectly.
  • Module declaration for the router instance: Instead of importing the Router instance everywhere (which bloats bundles and gets cumbersome), the recommended approach is declaring a Register interface via TypeScript module augmentation once, enabling router-aware autocomplete (e.g. in Link) app-wide without imports.
  • File-based routing preference: The TanStack Router Bundler Plugin automates three pain points of code-based routing: generating route configuration boilerplate, stitching the route tree together (including wiring getParentRoute), and automatic code-splitting of route components.

Code Examples

// src/app.tsx
declare module '@tanstack/react-router' {
  interface Register {
    router: typeof router
  }
}
  • What it demonstrates: Module declaration that lets the whole app get typesafe autocomplete for Link/navigate without importing the router instance.
// src/routes/posts/index.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/')({
  component: () => 'Posts index component goes here!!!',
})
  • What it demonstrates: How file-based routing eliminates the need to manually wire getParentRoute, stitch the route tree, or code-split, compared to the equivalent code-based route.

Key Takeaways

  1. Never try to define TanStack Router routes with JSX or a single nested-object tree, both defeat TypeScript inference and/or scalability; use code-based (createRoute + getParentRoute) or file-based routing instead.
  2. Declare the router instance once via TypeScript module declaration (declare module '@tanstack/react-router' { interface Register { router: typeof router } }) rather than importing it into every component.
  3. Prefer file-based routing for any application beyond a handful of routes; the Bundler Plugin removes boilerplate that would otherwise balloon a route-tree file to 700+ lines in a ~40-50 route app.

Connects To

  • Ch 1: Overview lists 100% inferred TypeScript support and file/code-based routing as headline features explained here in depth.
  • Ch 2: Quick Start's CLI prompt for file-based vs code-based routing maps directly to the trade-offs discussed in this chapter.
  • Ch 13/14: Migration chapters benefit from understanding why TanStack Router's route configuration differs from React Router/React Location.