skip to content

In TanStack Router, what is the difference between file-based routes generated by the bundler plugin and code-based routes built with createRoute?

level: juniorimportance: should knowfreq 38%

answer

  1. same route tree, different author
  2. a generated routeTree.gen.ts
  3. getParentRoute carries the types
  4. the plugin writes the path string

basics

~20 s

Both produce the same typed route tree. File-based routing lets the TanStack Router bundler plugin generate routeTree.gen.ts from files that call createFileRoute; code-based routing builds the tree by hand with createRootRoute, createRoute({ getParentRoute, path }) and addChildren.

solid answer

~40 s

TanStack Router always runs from a **route tree** passed to `createRouter({ routeTree })`. With **file-based routing**, the bundler plugin — `tanstackRouter()` from `@tanstack/router-plugin/vite`, listed before the React plugin — scans `src/routes`, where `__root.tsx` is the root and each file exports `Route = createFileRoute('/posts/$postId')({...})`, and generates `routeTree.gen.ts`. The plugin writes and updates the path string for you, and `autoCodeSplitting` can split route components automatically. With **code-based routing**, you call `createRootRoute()`, then `createRoute({ getParentRoute: () => parent, path: 'posts' })` for each route, and assemble them with `rootRoute.addChildren([...])`. `getParentRoute` is what lets child routes inherit parent params, search and context types. The docs recommend file-based routing for most apps; code-based suits generated or unusual trees.

code

ts · 11 lines
ts
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { tanstackRouter } from "@tanstack/router-plugin/vite";

export default defineConfig({
  plugins: [
    tanstackRouter({ target: "react", autoCodeSplitting: true }), // before react()
    react(),
  ],
});

go deeper

for a junior

Know the two styles: files with createFileRoute plus a generated routeTree.gen.ts, or createRoute calls wired with getParentRoute and addChildren.

for a middle

Explain what the plugin generates, why the createFileRoute path is managed for you, and why getParentRoute matters for inherited types.

for a senior

Pick the style for a codebase deliberately, keep generated files out of reviews and edits, and set naming conventions for pathless layouts and colocated files.

for a principal

Decide how route authoring scales across teams: generated trees and directory ownership versus programmatic trees for highly dynamic or generated route sets.

## One router, two ways to author the tree TanStack Router is a **type-safe** router for React: it infers the types of every route's path params, search params, loader data and context, and checks links against them. Whatever the authoring style, the router consumes a single **route tree** object: - `createRouter({ routeTree })` builds the router, - `<RouterProvider router={router} />` renders it. File-based and code-based routing are two ways of producing that `routeTree`. They support the same features; they differ in who writes the tree. ## File-based routing You describe routes as files under a routes directory (by default `src/routes`), and a **generator** turns them into code: 1. Install the bundler plugin and add `tanstackRouter({ target: "react", autoCodeSplitting: true })` from `@tanstack/router-plugin/vite` to the Vite config. The docs ask for it to be listed **before** the React plugin. 2. Create `src/routes/__root.tsx`, which exports the root route. 3. Add route files; each exports `export const Route = createFileRoute("/posts/$postId")({ component, loader, ... })`. 4. On dev and build, the plugin generates `src/routeTree.gen.ts`, which you import into `createRouter`. You never edit it. The path string passed to `createFileRoute` is **written and kept in sync by the plugin**: rename or move a file and the string updates. Naming conventions carry the structure: | File name feature | Meaning | |---|---| | `__root.tsx` | the root route | | `$postId` | a path param | | `index` | matches the parent's exact path | | `_pathlessLayout` | a layout route that adds no URL segment | | `.` in `posts.$postId.tsx` | nesting without a folder | | `-` prefix | excluded from the tree, for colocated files | | `(group)` folder | organises files without affecting the URL | | `route.tsx` in a folder | the route file for that folder's path | ## Code-based routing You build the same tree by hand: - `const rootRoute = createRootRoute({ component: Root })`, - `const postsRoute = createRoute({ getParentRoute: () => rootRoute, path: "posts" })`, - `const postRoute = createRoute({ getParentRoute: () => postsRoute, path: "$postId" })`, - `const routeTree = rootRoute.addChildren([postsRoute.addChildren([postRoute])])`. **`getParentRoute` is not optional plumbing.** It is how a child route's type learns about its parents, so that params, search params and context established higher up are typed lower down. Pathless layouts use an `id` instead of a `path`. ## What the generated file gives you `routeTree.gen.ts` is more than a list of imports. It wires every file route to its parent (the file-based equivalent of `getParentRoute`), assigns full paths and ids, and exports the typed `routeTree`. That is why file routes get complete types without any manual parent wiring, and why the generator must run before type-checking in CI. Most setups also mark the file read-only or exclude it from linting and formatting, since it is build output that happens to be committed or generated on demand. ## Choosing | | File-based | Code-based | |---|---|---| | Who writes the tree | the plugin generates `routeTree.gen.ts` | you, with `addChildren` | | Route path strings | managed by the plugin | typed by hand | | Automatic code splitting | yes, with `autoCodeSplitting` | manual | | Build tooling | bundler plugin or router CLI | none | | Recommended by the docs | yes, for most apps | when routes are generated or highly dynamic | ## Mistakes interviewers probe for - **Editing `routeTree.gen.ts`** by hand — the next generation overwrites it. - **Omitting `getParentRoute`** or pointing it at the wrong parent in code-based routes, which breaks inherited types. - **Treating the `createFileRoute` path as free text** — it must match the file's location; the plugin maintains it. - **Assuming file-based means framework-only** — the plugin works in a plain client-side Vite app; server rendering is a separate concern. A good one-sentence answer: *"Both give the router the same typed tree; file-based lets the plugin generate it from `createFileRoute` files, code-based means wiring `createRoute` calls with `getParentRoute` yourself."*

  • Why does code-based routing need getParentRoute on every route?
    Types flow down the tree. `getParentRoute` tells each child route who its parent is, so the child's type includes the parent's path params, validated search params and context. Without it, a child three levels down would lose what its layouts established, and the tree's shape would not be known either.
  • What happens if you edit routeTree.gen.ts by hand?
    The plugin or router CLI regenerates it on the next dev or build run, discarding the edit. Route structure changes belong in the route files and their names; the generated file should be treated as build output.

saying these in an interview costs you the question

  • routeTree.gen.ts is a starter file you customise by hand
  • The path passed to createFileRoute can be any label you like
  • getParentRoute is optional and only affects nesting at runtime
  • Code-based routes lose type safety compared with file-based routes
  • File-based routing requires a server-rendering framework