DEV Community

Cover image for How to install file-based Routing with TanStack Router, Vite, React, and TypeScript
Ignatium
Ignatium

Posted on

How to install file-based Routing with TanStack Router, Vite, React, and TypeScript

A step-by-step walkthrough for setting up TanStack Router's file-based routing in a new Vite + React + TypeScript project. By the end, adding a file to src/routes will add a page to your app.

Tech stack: Vite, React, TypeScript, TanStack Router, VS Code, pnpm

You'll need: Node.js and pnpm installed. The commands use pnpm, but npm or yarn work the same way.

1. Create a Vite + React + TypeScript project

pnpm create vite@latest project-folder-name --template react-ts
Enter fullscreen mode Exit fullscreen mode

The --template react-ts flag selects React and TypeScript for you, so Vite skips those questions. If it asks anything else (for example, whether to install dependencies and start the dev server), choose what you prefer.

If dependencies weren't installed, run:

cd project-folder-name
pnpm install
Enter fullscreen mode Exit fullscreen mode

2. Install the dependencies

Production dependency:

pnpm add @tanstack/react-router
Enter fullscreen mode Exit fullscreen mode

Dev dependencies:

pnpm add -D @tanstack/router-plugin @tanstack/router-cli @tanstack/react-router-devtools @types/node
Enter fullscreen mode Exit fullscreen mode

What each one does:

  • @tanstack/router-plugin: the Vite plugin that watches src/routes and generates your route tree
  • @tanstack/router-cli: provides the tsr command used in step 13
  • @tanstack/react-router-devtools: the devtools panel used in step 14
  • @types/node: Node.js type definitions

3. Configure the TanStack Router plugin

Open vite.config.ts and add the TanStack Router plugin.

Important: the TanStack Router plugin must be declared in plugins before react().

Setting autoCodeSplitting: true tells TanStack Router to automatically lazy-load your route components, so you don't have to split the code by hand.

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, // automatic lazy loading of route components
    }),
    react(),
  ],
})
Enter fullscreen mode Exit fullscreen mode

4. Make sure TypeScript includes the generated route tree

The generated routeTree.gen.ts file describes your whole routing hierarchy. Once TypeScript can see it, your route paths become type safe, which you'll try out in step 12.

Open tsconfig.app.json and check that the generated route tree is covered by include:

{
  // ...
  "include": [
    "src",
    "./src/routeTree.gen.ts" // Ensures TypeScript picks up the autogenerated tree instantly
  ]
}
Enter fullscreen mode Exit fullscreen mode

The Vite template's "src" entry already covers src/routeTree.gen.ts, so the second line is a safeguard rather than a requirement. It's useful if you've changed include in your own project.

5. Create the routes folder

Inside src/, create the following folder and files:

src/routes
src/routes/__root.tsx
src/routes/index.tsx
Enter fullscreen mode Exit fullscreen mode

The TanStack Router plugin watches this folder. Every file you add here becomes a route.

6. Start the dev server

pnpm dev
Enter fullscreen mode Exit fullscreen mode

When the dev server starts, the TanStack Router plugin does two things automatically:

  • Populates __root.tsx and index.tsx with starter code
  • Generates a new file, src/routeTree.gen.ts

You won't see any change in the browser yet. That comes after step 9.

7. Understand the <Outlet /> component

Do not skip this section before moving on. Open src/routes/__root.tsx the TanStack plugin has filled it in with something like this:

import * as React from 'react'
import { Outlet, createRootRoute } from '@tanstack/react-router'

export const Route = createRootRoute({
  component: RootComponent,
})

function RootComponent() {
  return (
    <React.Fragment>
      <div>Hello "__root"!</div>
      <Outlet />
    </React.Fragment>
  )
}
Enter fullscreen mode Exit fullscreen mode

<Outlet /> is a placeholder. It marks the spot where TanStack Router renders the component of the child route that matches the current URL. Everything around it, here the Hello "__root"! line, belongs to the parent and stays on screen no matter which child is showing. Only the area where <Outlet /> sits changes as you navigate.

Now open src/routes/index.tsx. It looks something like this:

import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/')({
  component: RouteComponent,
})

function RouteComponent() {
  return <div>Hello "/"!</div>
}
Enter fullscreen mode Exit fullscreen mode

This file has no <Outlet /> because it has no children. It is the end of the line. When you visit /, the router matches __root and then uses index (all index.tsx files are used as a default page when visiting the root of a path unless otherwise specified) , and renders the root's component with the index component dropped into its <Outlet />:

/
└── __root.tsx     renders: Hello "__root"!  + <Outlet />
    └── index.tsx  renders into the Outlet: Hello "/"!
Enter fullscreen mode Exit fullscreen mode

Parent routes in the path tree

__root.tsx is the parent of every route in your app, but any route can be a parent. You don't need to create these files now. This example only shows how the <Outlet /> component works, and helps to understand the routers tree structure:

src/routes/
├── __root.tsx       ← (primary node for all routes "/")
├── index.tsx        ← (default file rendered when visiting "/")
└── dashboard/
    ├── route.tsx       ← /dashboard  (this acts as the PARENT/LAYOUT route)
    └── index.tsx       ← /dashboard/ (default file rendered when visiting "/dashboard")
    └── settings.tsx     ← /dashboard/settings
Enter fullscreen mode Exit fullscreen mode

The parent, dashboard/route.tsx, will render its own content plus an <Outlet /> for its children:

import { createFileRoute, Outlet } from '@tanstack/react-router'

export const Route = createFileRoute('/dashboard')({
  component: RouteComponent,
})

function RouteComponent() {
  return (
  <>
    <div>Route.tsx</div>
    {/**
       * By default dashboard/index.tsx will automatically be rendered
       * through "<Outlet />" when visiting "localhost:5173/dashboard". 
       */}
     <Outlet />
 </>)
}

Enter fullscreen mode Exit fullscreen mode

When you visit /dashboard/settings, each parent passes the page down to the next one through its <Outlet />:

/dashboard/settings
└── __root.tsx               always rendered, shows its <Outlet />
    └── route.tsx              the "Dashboard" heading, shows its <Outlet />
        └── dashboard/settings.tsx   the settings page
Enter fullscreen mode Exit fullscreen mode

A few things to remember:

  • A parent without <Outlet /> hides its children. If dashboard/route.tsx left out <Outlet />, /dashboard/settings would show only the dashboard/route.tsx content, and the settings page would never appear.
  • Parents are for shared layout. Put things every child needs, such as a navigation menu, a sidebar or a page title, in the parent and let <Outlet /> swap the content.

8. Protect the generated route tree file

src/routeTree.gen.ts is generated for you and should never be edited by hand. To protect it, add the following to your VS Code .vscode/settings.json (create the file if it doesn't exist):

{
  // ...your other settings

  "files.readonlyInclude": {
    "**/routeTree.gen.ts": true
  },
  "files.watcherExclude": {
    "**/routeTree.gen.ts": true
  },
  "search.exclude": {
    "**/routeTree.gen.ts": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Click the file in the VS Code file explorer and you should see a lock icon to the right of its name.

9. Wire up the router in App.tsx

Replace the contents of src/App.tsx with:

import { RouterProvider, createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";

const router = createRouter({ routeTree });

declare module "@tanstack/react-router" {
  interface Register {
    router: typeof router;
  }
}

function App() {
  return <RouterProvider router={router} />;
}

export default App;
Enter fullscreen mode Exit fullscreen mode

The declare module block registers your router with TypeScript, so links and route paths are type-checked across the whole app.

10. See it in the browser

Open http://localhost:5173 in your browser. You should now see:

Hello "__root"!
Hello "/"!
Enter fullscreen mode Exit fullscreen mode

What's happening:

  • __root.tsx renders the first line. The root route always appears, no matter which page you're on, which makes it the place for things like your navigation menu and website logo. Open the file and you'll find an <Outlet /> component (explained in step 7). This is where the currently matched route is rendered.
  • index.tsx renders the second line, through the __root.tsx route's <Outlet />. It's the default route, shown at the root path, localhost:5173/.

11. Test the setup

Create about.tsx and info.tsx in the src/routes folder.

The TanStack Router plugin configured in vite.config.ts automatically populates these files with starter code and registers them in routeTree.gen.ts. That in turn updates the router used by <RouterProvider> in App.tsx, so there's nothing else to wire up.

Go to your browser and navigate to localhost:5173/about and localhost:5173/info. (If Vite chose a different port, use the one shown in your terminal.)

12. Try type-safe navigation

One of the best things about TanStack Router is that your route paths are type checked. Let's see it in action.

Create test-routes.tsx in the src/routes folder. The plugin fills it with starter code, so replace everything in it with:

import { createFileRoute, Link, useNavigate } from '@tanstack/react-router'

export const Route = createFileRoute('/test-routes')({
  component: RouteComponent,
})

function RouteComponent() {
  const navigate = useNavigate()

  return (
    <>
      <div>Hello "/test-routes"!</div>

      {/* Declarative navigation with <Link> */}
      <Link to="/info">Go to Info</Link>

      {/* Programmatic navigation with useNavigate */}
      <button type="button" onClick={() => navigate({ to: '/some-file' })}>
        Go to Some File
      </button>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

Notice that to: '/some-file' is flagged as an error in your editor. There is no some-file.tsx in src/routes, so the path doesn't exist, and TypeScript/TanStack catches the mistake before you run the app. The error shows up in your editor and in tsc, not in the browser.

Now change '/some-file' to '/info', or delete the text between the quotes and trigger autocomplete. Your editor will list every route in your app, and the error disappears. The to prop on <Link> is type safe in the same way.

Visit localhost:5173/test-routes to try both.

13. Generate routes without the dev server

You can also generate the route tree manually when the dev server isn't running. Add these to the scripts section of your package.json:

{
  "scripts": {
    "generate-routes": "tsr generate",
    "watch-routes": "tsr watch"
  }
}
Enter fullscreen mode Exit fullscreen mode
  • pnpm generate-routes generates routeTree.gen.ts once.
  • pnpm watch-routes regenerates it whenever your route files change.

You won't need these day to day, because pnpm dev already keeps the route tree up to date. They're handy for troubleshooting, for example if the generated file gets out of sync.

14. Add the TanStack Router devtools

The devtools are a great way to see how your routes fit together. Modify src/routes/__root.tsx to look like this:

import * as React from 'react'
import { Outlet, createRootRoute } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'

export const Route = createRootRoute({
  component: RootComponent,
})

function RootComponent() {
  return (
    <React.Fragment>
      <div>Hello "__root"!</div>
      <Outlet />
      <TanStackRouterDevtools position="bottom-right" />
    </React.Fragment>
  )
}
Enter fullscreen mode Exit fullscreen mode

A devtools button will appear at the bottom right of your browser. Click it to see the tree hierarchy of the routes (all pages descend from __root.tsx) in your website and how they connect. It's an excellent aid for learning how TanStack Router works.

Wrap-up

That's it. You now have file-based routing with TanStack Router running on Vite, React, and TypeScript, where adding a file to src/routes adds a page to your app.

If you have any suggestions, leave a comment and I'll do my best to respond. Thanks for reading!

Top comments (0)