Set Up Client-Side Routing
So far in our React apps, every app has been a single page. But most real web applications have multiple pages. These might be a home page, an about page, or a detail page for individual items. In a traditional website, navigating between pages means a full page reload. The browser requests a new HTML document from the server, and the entire page is rebuilt from scratch.
Client-side routing takes a different approach. Instead of asking the server for a new page, the router intercepts navigation and swaps out just the parts of the UI that change. All of this happens in the browser, so it is fast. The URL still updates (so bookmarks and the back button work), but the page never fully reloads.
We will use TanStack Router, a type-safe routing library for React. It uses file-based routing. You create files in a routes/ directory, and the router automatically generates the route configuration for you.
Install TanStack Router
Install the router and its Vite plugin:
pnpm add @tanstack/react-router
pnpm add -D @tanstack/router-plugin
Update vite.config.ts to add the TanStack Router plugin. It must come before the React plugin:
If you are deploying to GitHub Pages, replace kudoboard below with your repository name. The examples use /kudoboard/ because that is the name of the original template repository.
import path from "path";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import { tanstackRouter } from "@tanstack/router-plugin/vite"; // 👀
// https://vite.dev/config/
export default defineConfig({
base: "/kudoboard/",
plugins: [
// 👀 Make sure that 'tanstackRouter' is passed before 'react'
tanstackRouter({
target: "react",
autoCodeSplitting: true,
}),
react(),
tailwindcss(),
],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});
Create the routes directory
Create a src/routes/ directory. This is where all our route files will live:
mkdir src/routes
The Vite plugin watches this directory and automatically generates a route tree file (src/routeTree.gen.ts). You never edit this file directly, but you should commit it to version control. The plugin also generates a .tanstack directory to store additional generated code for file-based routing. You should add .tanstack to your .gitignore file to avoid committing generated code to version control.
The root layout
Every app needs a root layout. It is the UI that wraps every page. This is where you put things like a navigation bar and a footer. In TanStack Router, you define this in a special file called __root.tsx.
Create src/routes/__root.tsx:
import { createRootRoute, Link, Outlet } from "@tanstack/react-router";
export const Route = createRootRoute({
component: RootLayout,
});
function RootLayout() {
return (
<div className="min-h-screen bg-background text-foreground">
<header className="border-b">
<nav className="mx-auto flex max-w-5xl items-center justify-between px-4 py-3">
<Link to="/" className="text-xl font-bold">
KudoBoard
</Link>
</nav>
</header>
<main className="mx-auto max-w-5xl px-4 py-8">
<Outlet />
</main>
</div>
);
}
There are two key concepts here:
-
<Link>replaces the standard<a>tag. It navigates between routes without a full page reload. Thetoprop specifies the target route. -
<Outlet />is a placeholder that renders the matching child route. When you are at/, it renders the home page component. When you are at/about, for example, it renders the about page. The layout (header, nav, main wrapper) stays the same. Only the<Outlet />content changes.
Update the entry point
Now replace the old App-based entry point with the router. Update src/main.tsx:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { createRouter, RouterProvider } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
import "./index.css";
const router = createRouter({ routeTree, basepath: "/kudoboard" });
declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}
createRoot(document.getElementById("root")!).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
);
- The
routeTreeis auto-generated from your route files - The
basepathmatches thebaseinvite.config.ts(needed for GitHub Pages deployment). If your repository name is different, update both values to match it. - The
declare moduleblock registers the router’s types globally, giving you type-safe navigation throughout the app.
Finally, delete src/App.tsx. The router’s root layout replaces it. Then run the dev server. You should see a page with a header and a “KudoBoard” title, but no content. That is the root layout rendering.

Checkpoint: Commit your progress.
git add .
git commit -m "kudoboard-01: Set up TanStack Router with file-based routing"
git push