Set up TanStack Start with Nitro for a full-stack React framework experience with server-side rendering, file-based routing, and integrated API routes.
- Add the Nitro Vite plugin to your Vite config
- Create a server entry using TanStack Start's server handler
- Configure the router with default components
- Define routes and API endpoints using file-based routing
Add the Nitro, React, TanStack Start, and Tailwind plugins to your Vite config:
import { defineConfig } from "vite";
import { nitro } from "nitro/vite";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import viteReact from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [tanstackStart(), viteReact(), tailwindcss(), nitro()],
resolve: { tsconfigPaths: true },
environments: {
ssr: { build: { rollupOptions: { input: "./server.ts" } } },
},
});The tanstackStart() plugin provides full SSR integration with automatic client entry handling. The environments.ssr option points to the server entry file.
Create a server entry that uses TanStack Start's handler:
import handler, { createServerEntry } from "@tanstack/react-start/server-entry";
export default createServerEntry({
fetch(request) {
return handler.fetch(request);
},
});TanStack Start handles SSR automatically. The createServerEntry wrapper integrates with Nitro's server entry format, and the handler.fetch processes all incoming requests.
Create a router factory function with default error and not-found components:
import { createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen.ts";
export function getRouter() {
const router = createRouter({
routeTree,
defaultPreload: "intent",
defaultErrorComponent: () => <div>Internal Server Error</div>,
defaultNotFoundComponent: () => <div>Not Found</div>,
scrollRestoration: true,
});
return router;
}The router factory configures preloading behavior, scroll restoration, and default error/not-found components.
The root route defines your HTML shell with head management and scripts:
/// <reference types="vite/client" />
import { HeadContent, Link, Scripts, createRootRoute } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import * as React from "react";
import appCss from "~/styles/app.css?url";
export const Route = createRootRoute({
head: () => ({
meta: [
{ charSet: "utf8" },
{ name: "viewport", content: "width=device-width, initial-scale=1" },
],
links: [{ rel: "stylesheet", href: appCss }],
scripts: [{ src: "/customScript.js", type: "text/javascript" }],
}),
errorComponent: () => <h1>500: Internal Server Error</h1>,
notFoundComponent: () => <h1>404: Page Not Found</h1>,
shellComponent: RootDocument,
});
function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html>
<head>
<HeadContent />
</head>
<body>
<div className="p-2 flex gap-2 text-lg">
<Link to="/" activeProps={{ className: "font-bold" }} activeOptions={{ exact: true }}>
Home
</Link>{" "}
<Link
// @ts-ignore
to="/this-route-does-not-exist"
activeProps={{ className: "font-bold" }}
>
404
</Link>
</div>
<hr />
{children}
<TanStackRouterDevtools position="bottom-right" />
<Scripts />
</body>
</html>
);
}Define meta tags, stylesheets, and scripts in the head() function. The shellComponent provides the HTML document shell that wraps all pages. Use HeadContent to render the head configuration and Scripts to inject the client-side JavaScript for hydration.
Page routes define your application pages:
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/")({ component: Home });
function Home() {
return (
<div className="p-2">
<h3>Welcome Home!</h3>
<a href="/api/test">/api/test</a>
</div>
);
}TanStack Start supports API routes alongside page routes. Create files in src/routes/api/ to define server endpoints that Nitro serves automatically.