Nitro logoNitro

SSR with TanStack Router

Client-side routing with TanStack Router in Nitro using Vite.
src/main.tsx
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider, createRouter } from "@tanstack/react-router";

// Import the generated route tree
import { routeTree } from "./routeTree.gen.ts";

// Create a new router instance
const router = createRouter({ routeTree });

// Register the router instance for type safety
declare module "@tanstack/react-router" {
  interface Register {
    router: typeof router;
  }
}

// Render the app
const rootElement = document.querySelector("#root")!;
if (!rootElement.innerHTML) {
  const root = ReactDOM.createRoot(rootElement);
  root.render(
    <StrictMode>
      <RouterProvider router={router} />
    </StrictMode>
  );
}

Set up TanStack Router with React, Vite, and Nitro. This setup provides file-based routing with type-safe navigation and automatic code splitting.

Overview

Add the Nitro Vite plugin to your Vite config

Create an HTML template with your app entry

Create a main entry that initializes the router

Define routes using file-based routing

1. Configure Vite

Add the Nitro, React, and TanStack Router plugins to your Vite config:

vite.config.mjs
import { defineConfig } from "vite";
import { nitro } from "nitro/vite";
import react from "@vitejs/plugin-react";
import { tanstackRouter } from "@tanstack/router-plugin/vite";

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

The tanstackRouter plugin generates a route tree from your routes/ directory structure. Enable autoCodeSplitting to automatically split routes into separate chunks. Place the TanStack Router plugin before the React plugin in the array.

2. Create the HTML Template

Create an HTML file that serves as your app shell:

index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Nitro + TanStack Router + React</title>
  </head>

  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

3. Create the App Entry

Create the main entry that initializes TanStack Router:

main.tsx
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider, createRouter } from "@tanstack/react-router";

// Import the generated route tree
import { routeTree } from "./routeTree.gen.ts";

// Create a new router instance
const router = createRouter({ routeTree });

// Register the router instance for type safety
declare module "@tanstack/react-router" {
  interface Register {
    router: typeof router;
  }
}

// Render the app
const rootElement = document.querySelector("#root")!;
if (!rootElement.innerHTML) {
  const root = ReactDOM.createRoot(rootElement);
  root.render(
    <StrictMode>
      <RouterProvider router={router} />
    </StrictMode>
  );
}

The routeTree.gen.ts file is auto-generated from your routes/ directory structure. The Register interface declaration provides full type inference for route paths and params. The !rootElement.innerHTML check prevents re-rendering during hot module replacement.

4. Create the Root Route

The root route (__root.tsx) defines your app's layout:

__root.tsx
import { createRootRoute, Link, Outlet } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";

const RootLayout = () => (
  <>
    <div className="p-2 flex gap-2">
      <Link to="/" className="[&.active]:font-bold">
        Home
      </Link>
    </div>
    <hr />
    <Outlet />
    <TanStackRouterDevtools />
  </>
);

export const Route = createRootRoute({ component: RootLayout });

Use Link for type-safe navigation with active state styling. The Outlet component renders child routes. Include TanStackRouterDevtools for development tools (automatically removed in production).

5. Create Page Routes

Page routes use createFileRoute and can include loaders:

index.tsx
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/")({
  loader: async () => {
    const r = await fetch("/api/hello");
    return r.json();
  },
  component: Index,
});

function Index() {
  const r = Route.useLoaderData();

  return (
    <div className="p-2">
      <h3>{JSON.stringify(r)}</h3>
    </div>
  );
}

Fetch data before rendering with the loader function—data is available via Route.useLoaderData(). File paths determine URL paths: routes/index.tsx maps to /, routes/about.tsx to /about, and routes/users/$id.tsx to /users/:id.

Learn More