TanStack Start is a full-stack React framework built on TanStack Router, Vite, and Nitro. It provides type-safe file-based routing, streaming SSR, and server functions (typed RPC calls whose code runs only on the server). Nitro builds the app into a portable server bundle for Node, Bun, Cloudflare, Netlify, and other hosts. It uses React 19 and TypeScript and can run as a SPA. It is on the 1.168.x line; the official docs still label it a Release Candidate but call its API stable. Our MakerKit TanStack Start kit is built on it.
If you already use TanStack Query or TanStack Table, TanStack Start is the framework from the same team. It adds a router and a server runtime to those libraries so you can build a complete application with them, instead of adding them to another framework.
This guide covers what TanStack Start is made of, the four primitives you write every day, and where it falls short. The code examples come from our production kit, with file paths.
What Is TanStack Start Built On?
TanStack Start combines three existing projects:
- TanStack Router handles routing. Route params, search params, and loader data are typed end to end. The route tree types are generated for you, so you do not maintain them by hand.
- Vite is the build tool and dev server. You get Vite's HMR and its plugin ecosystem.
- Nitro is the server runtime. It compiles the app into a server bundle and has deployment presets for Node, Bun, Cloudflare, Netlify, and other hosts.
The framework renders on the server, streams HTML, and hydrates on the client. You can also turn SSR off and ship a SPA.
This is the router entry point in the kit. It connects the router to TanStack Query at the root:
// apps/web/src/router.tsximport { createRouter as createTanStackRouter } from '@tanstack/react-router';import { QueryClient } from '@tanstack/react-query';import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query';import { routeTree } from './routeTree.gen';export function getRouter() { const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 60_000 } }, }); const router = createTanStackRouter({ routeTree, context: { queryClient }, defaultPreload: 'intent', }); setupRouterSsrQueryIntegration({ router, queryClient }); return router;}routeTree.gen is generated from your route files. defaultPreload: 'intent' preloads a route's code and data when the user hovers or focuses a link, so most navigations have their data ready before the click.
Where TanStack Start Came From
TanStack Start is the framework layer for the TanStack libraries. TanStack began with React Query (now TanStack Query) and added Table, Form, Virtual, and Router. Each library solves one problem and works with any framework, which left developers to assemble the rest of the application themselves.
Start supplies those missing parts (routing, SSR, server code, and the production build) on top of TanStack Router. If you add TanStack Query to every project, Start gives you the rest of the application from the same team, with the same approach to types.
Core Concepts: Routing, Loaders, Server Functions, Query
Four primitives cover most of the code in a TanStack Start app.
1. File-based, type-safe routing
Each route is a file that exports a Route created with createFileRoute. The path string is checked against your file tree at compile time:
// apps/web/src/routes/_authenticated/dashboard/index.tsximport { createFileRoute } from '@tanstack/react-router';export const Route = createFileRoute('/_authenticated/dashboard/')({ component: DashboardPage,});The _authenticated prefix marks a pathless layout route: it wraps its children in an auth guard without adding a URL segment. Params and loader data keep their types, so Route.useLoaderData() in the component returns exactly the type the loader produced.
2. Loaders
A loader fetches data before its route renders. It runs on the server during SSR and on the client during navigation, and its return value is typed:
export const Route = createFileRoute('/_authenticated/dashboard/')({ loader: async () => { const context = await fetchAccountContext(); if (context.isOrganization) { const organization = await fetchActiveOrganization(); return { organizationName: organization?.name ?? null }; } return { organizationName: null }; }, head: ({ loaderData }) => ({ meta: [{ title: loaderData?.organizationName ?? 'Dashboard' }], }), component: DashboardPage,});This is the dashboard loader from the kit (apps/web/src/routes/_authenticated/dashboard/index.tsx). The head function builds page metadata from the loader's data, which gives each route its own title without a separate metadata API.
3. Server functions
A server function is defined with createServerFn. TanStack Start compiles it into an RPC endpoint, and you call it like any async function from client or server code. The handler body runs only on the server.
// apps/web/src/lib/theme.functions.tsimport { createServerFn } from '@tanstack/react-start';import { getCookie } from '@tanstack/react-start/server';import { parseTheme, THEME_COOKIE } from './theme';export const getServerTheme = createServerFn({ method: 'GET' }).handler(() => { return parseTheme(getCookie(THEME_COOKIE));});Because the handler runs on the server, it can read cookies, query the database, and use secrets, and neither that code nor its imports reach the browser bundle. The kit uses this pattern throughout. For example, apps/web/src/lib/auth/account.functions.ts wraps request-scoped auth helpers in server functions so route loaders can read account data during client-side navigation.
The closest Next.js equivalents are Server Actions and Route Handlers. The difference is that a server function is a typed function you import and call, with its HTTP method (GET or POST), validation, and middleware declared on the definition.
4. Data fetching with TanStack Query
TanStack Query is built into the kit's router setup. The setupRouterSsrQueryIntegration call in router.tsx sends the query cache from the server to the client, so data fetched during SSR is available to useQuery without a second request. Loaders prefetch into the cache, and components read from it with the usual Query hooks. One library handles SSR data, client caching, refetching, and mutations.
Common Pitfalls When Starting with TanStack Start
These are the problems we hit while building the kit:
- Wrapping
createServerFnin a factory. The compiler detects server functions by the literalcreateServerFn(...)call. If a helper returns the builder, the compiler cannot strip the handler, and its server-only imports (database clients, auth) can end up in the client bundle. CallcreateServerFndirectly at each definition site. - Treating loaders as server-only. Loaders also run in the browser on client-side navigation. Anything that needs cookies, secrets, or the database belongs in a server function the loader calls, as the dashboard loader above does with
fetchAccountContext. - Using only a route guard for authorization. A
beforeLoadguard on_authenticatedcontrols navigation, but server functions are public HTTP endpoints. Check auth in server function middleware too. Our Better Auth + TanStack Start guide shows both checks. - Editing
routeTree.gen.tsby hand. The file is regenerated from your route files, so manual changes are overwritten.
How Is TanStack Start Different from Next.js and React Router?
TanStack Start differs from Next.js mainly in its data model and build pipeline: loaders and server functions instead of React Server Components, and Vite plus Nitro instead of Turbopack and Vercel-oriented output. It differs from React Router mainly in how far type safety goes.
- vs. Next.js. Next.js 16 builds on React Server Components, defaults to Turbopack, and is designed around Vercel deployment. Next.js offers opt-in
typedRoutesfor links, while TanStack Router also types params, search params, and loader data. Our TanStack Start vs Next.js comparison covers routing, data fetching, deployment, and RSC dimension by dimension, and the Next.js 16 guide covers the current Next.js release. - vs. React Router (and Remix, which merged into it). React Router also has loaders, actions, and SSR. TanStack Router's type safety extends further, especially for validated search params, and Start is integrated more closely with TanStack Query.
Is TanStack Start Production Ready?
Yes. TanStack Start is stable enough for production SaaS, with the limits you would expect from a framework younger than Next.js.
Our production SaaS starter kit runs on it: it builds, passes typechecking and Playwright E2E tests, and deploys as a Nitro server bundle.
The limits:
- Smaller community. There are fewer Stack Overflow answers and blog posts, so expect to read the docs and source code more often than with Next.js.
- Release Candidate label. As of October 2026 the official docs still call TanStack Start a Release Candidate, while stating that its API is considered stable. The npm
latesttag is on the 1.168.x line. - Faster-moving APIs. New releases ship every few days. Pin versions and read release notes before upgrading.
- Fewer ready-made integrations. Many third-party services publish a Next.js guide first and a TanStack Start guide later, if at all.
- React Server Components are experimental. RSC support is opt-in (through
@vitejs/plugin-rsc) and marked experimental in the official docs. It is not what the framework's defaults or our kit are built around.
Use TanStack Start if your team knows Vite and TanStack Query and does not need a large template marketplace. Pick Next.js if you need a copy-paste guide for every third-party integration or you rely on RSC today.
Building a SaaS with TanStack Start
TanStack Start gives you routing, loaders, and server functions. A SaaS also needs authentication, organizations, permissions, billing, and an admin panel, and the framework does not include any of them. In the MakerKit TanStack Start kit, each of those features is built on the primitives described above.
Server function middleware is where most of the authorization lives. This is the function that changes a member's role in an organization:
// packages/organization/ui/src/lib/server/functions/members.functions.ts (excerpt)import { createServerFn } from '@tanstack/react-start';import { authFunctionMiddleware } from '@kit/function-middleware/functions';import { withFeaturePermission } from '@kit/function-middleware/server';import { requireActiveOrganizationId } from '@kit/better-auth/context';import { updateMemberRoleSchema } from '@kit/organization-core/schemas/members.schema';import { createMembershipsService } from '@kit/organization-core/services';export const updateMemberRoleFunction = createServerFn({ method: 'POST' }) .middleware([ ...authFunctionMiddleware, withFeaturePermission({ member: ['update'] }), ]) .validator(updateMemberRoleSchema) .handler(async ({ data, context }) => { const organizationId = await requireActiveOrganizationId(); const membershipsService = createMembershipsService(); await membershipsService.updateMemberRole({ userId: context.user.id, organizationId, memberId: data.memberId, newRole: data.role, }); return { success: true, }; });The middleware runs in order before the handler:
errorMiddleware(first item ofauthFunctionMiddleware) catches errors thrown later in the chain and passes them through the kit's auth error handler, which keeps safe Better Auth error codes and masks everything else.redirect()andnotFound()pass through unchanged.authMiddleware(second item) reads the Better Auth session from the request, throwsUnauthorizedif there is none, and addsuserandsessiontocontext. That is wherecontext.user.idin the handler comes from.withFeaturePermission({ member: ['update'] })resolves the active organization, asks Better Auth'shasPermissionAPI whether the current user's role grantsupdateonmemberin that organization, and throwsUnauthorizedif it does not.
The permission check sits on the function because the function is a public HTTP endpoint. The _authenticated route guard only stops navigation to the members page; a signed-in user with the member role could still POST to this function directly. With the check in middleware, that request fails before the handler runs. This is the fix for the "route guard only" pitfall above.
Other parts of the kit that build on the same primitives:
- Organizations and RBAC. Members, invitations, and roles, enforced by middleware like the example above.
- Billing. Subscription checkout and cancellation run as server functions with
authFunctionMiddleware. - Data layer variants. Drizzle or Prisma with Better Auth, or Supabase with Supabase Auth and row-level security.
- Nitro deployment. The app builds into a server bundle that runs on any Node host.
The launch post lists what each variant ships with, and TanStack Start: Drizzle or Prisma helps you pick a data layer.
Who Should Use TanStack Start?
TanStack Start fits teams that:
- Already use TanStack Query and want routing, SSR, and server code from the same stack.
- Want type safety across routes, params, search params, and server calls.
- Deploy outside Vercel (Cloudflare, Node, Bun, a VPS, Docker).
- Prefer Vite's dev server and plugins.
It fits less well if your app depends on React Server Components now, or if hiring from the largest possible pool of experienced developers matters more than the points above.
To start, scaffold a project from the TanStack Start docs if you want to build the SaaS layer yourself. If you want auth, billing, and teams already working, start from the MakerKit TanStack Start kit: it is the codebase every example in this post came from, and it ships in Drizzle, Prisma, and Supabase variants.
Frequently Asked Questions
What is TanStack Start?
Is TanStack Start production ready?
Does TanStack Start replace Next.js?
What is a server function in TanStack Start?
Is TanStack Start free and open source?
Does TanStack Start use React Server Components?
Is there a TanStack Start SaaS starter kit?
Based on TanStack Start 1.168, TanStack Router 1.170, and React 19.3, checked October 2026.