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
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
2. Install the dependencies
Production dependency:
pnpm add @tanstack/react-router
Dev dependencies:
pnpm add -D @tanstack/router-plugin @tanstack/router-cli @tanstack/react-router-devtools @types/node
What each one does:
-
@tanstack/router-plugin: the Vite plugin that watchessrc/routesand generates your route tree -
@tanstack/router-cli: provides thetsrcommand 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
pluginsbeforereact().
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(),
],
})
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
]
}
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
The TanStack Router plugin watches this folder. Every file you add here becomes a route.
6. Start the dev server
pnpm dev
When the dev server starts, the TanStack Router plugin does two things automatically:
- Populates
__root.tsxandindex.tsxwith 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>
)
}
<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>
}
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 "/"!
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
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 />
</>)
}
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
A few things to remember:
-
A parent without
<Outlet />hides its children. Ifdashboard/route.tsxleft out<Outlet />,/dashboard/settingswould show only thedashboard/route.tsxcontent, 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
}
}
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;
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 "/"!
What's happening:
-
__root.tsxrenders 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.tsxrenders the second line, through the__root.tsxroute'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>
</>
)
}
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"
}
}
-
pnpm generate-routesgeneratesrouteTree.gen.tsonce. -
pnpm watch-routesregenerates 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>
)
}
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)