Nitro V3 + Tanstack Router + React
Nitro V3 is out now with big improvements. Nitro now comes as a Vite plugin, so you can use it inside any Vite project. You can also bring your own backend framework and run it through Nitro. This means you can use tools like tRPC, Elysia, or Hono with the server entry point and build full stack apps using Vite.
By default, Nitro V3 uses rendu for templates. In this guide, we’ll replace rendu with React + TanStack Router.
Let’s start by creating a new Nitro V3 project. Make sure to pick the Full Stack With Vite option.
bunx create-nitro-app
Go into your new project and install the needed packages for React and TanStack Router:
bun add react react-dom @tanstack/react-router @tanstack/react-router-devtoolsbun add -D @tanstack/router-plugin @types/react @types/react-dom @vitejs/plugin-react
After installing, add the TanStack Router and React plugins to your vite.config.ts
file with the nitro
plugin:
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(), ], nitro: { preset: "standard", },});
Next, update your tsconfig.json
file to support JSX, add Vite types, and set up import aliases for the src
directory.
{ "compilerOptions": { // Module resolution "target": "ESNext", "module": "ESNext", "moduleResolution": "Bundler",
// JSX "jsx": "preserve", "jsx": "react-jsx", "jsxFactory": "h", "jsxFragmentFactory": "Fragment",
// Core checks "strict": true, "noEmit": true, "skipLibCheck": true, "resolveJsonModule": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true,
// Additional safety "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "useUnknownInCatchVariables": true, "noUnusedLocals": true, "types": ["vite/client"],
"baseUrl": ".", "paths": { "@/*": ["src/*"] } }, "include": ["src", "types.d.ts"], "exclude": ["node_modules", "dist"]}
You should already have a src
folder in your project. We need to create three files inside it to set up Tanstack Router.
src/routes/__root.tsx
(with two ’_’ characters)src/routes/index.tsx
src/main.tsx
(there should already be a src/main.ts file, we’ll rename it tosrc/main.tsx
)
src/routes/__root.tsx
is the root route.
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>{' '} <Link to="/about" className="[&.active]:font-bold"> About </Link> </div> <hr /> <Outlet /> <TanStackRouterDevtools /> </>)
export const Route = createRootRoute({ component: RootLayout })
src/routes/index.tsx
is the index route. You can see it makes a request to /api/hello
which is already set up in the routes/api/hello.ts
file.
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> );}
src/main.tsx
is the main entry point. Rename src/main.ts
to src/main.tsx
and add the code below. You can delete the src/app.ts
file now since we don’t need it anymore.
import { StrictMode } from 'react'import ReactDOM from 'react-dom/client'import { RouterProvider, createRouter } from '@tanstack/react-router'
// Import the generated route treeimport { routeTree } from './routeTree.gen'
// Create a new router instanceconst router = createRouter({ routeTree })
// Register the router instance for type safetydeclare module '@tanstack/react-router' { interface Register { router: typeof router }}
// Render the appconst rootElement = document.getElementById('root')!if (!rootElement.innerHTML) { const root = ReactDOM.createRoot(rootElement) root.render( <StrictMode> <RouterProvider router={router} /> </StrictMode>, )}
Update the routes/api/hello.ts
file to return a JSON response.
import { defineHandler } from "nitro/h3";
export default defineHandler((event) => { return { hello: "API" };});
Update the index.html
file in the project root to use the main.tsx
file.
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>My Nitro + TanStack Router + React App</title> </head>
<body> <div id="root"></div> <script type="module" src="/src/main.tsx"></script> </body></html>
When you’re done with all the changes, run bun run dev
to start the development server. Go to http://localhost:3000
and you should see the app running with the Hello API
message.