Link to headingBuild full-stack React with TanStack Start
Routing in a modern React app is rarely as simple as matching a URL to a component. The route starts carrying filters, pagination, selected entities, preload timing, pending states, and server calls. If those pieces live in separate systems, every navigation becomes coordination work.
TanStack Start takes a straightforward position: the route tree should own that coordination. It is TanStack Router as a full-stack framework, with route loaders, server functions, streaming SSR, full-document rendering, and full-stack builds around it.
That position makes Start feel different from frameworks where the server component tree is the center of gravity. In Start, the route tree owns path params, search params, preloading, data dependencies, error states, document rendering, and the typed bridge into server-only code. React components stay interactive by default, and server calls stay explicit.
Link to headingRoutes define your app's shape
TanStack Start is TanStack Router running as the framework layer, with Start adding the server entry, client entry, SSR pipeline, server functions, and deployment adapters around it.
The root route owns the document shell. In a typical project, src/routes/__root.tsx returns the full HTML document: <HeadContent /> renders route-managed metadata inside <head>, <Outlet /> renders the matched child route, and <Scripts /> injects the client scripts before the closing </body> tag. Child routes live under src/routes, and the generated route tree gives TypeScript enough structure to infer route params, search params, loader data, and route-specific hooks.
That design gives Start a clear mental model: a route file is where URL validation, data loading, pending states, server handlers, and component rendering all come together.
Link to headingSetup stays close to the build tool
You can start with TanStack Builder, the TanStack CLI, or an official example. A basic file-based project follows the usual Start shape:
src/ router.tsx routeTree.gen.ts routes/ __root.tsx index.tsx posts/ $postId.tsxThe routing architecture is divided into three distinct layers:
routeTree.gen.ts: Automatically generated by the TanStack Router plugin to map the application's route graph.router.tsx: Creates the router instance from the generated tree and configures global behavior, preloading, context, and cache defaults.__root.tsx: Owns the foundational document shell and shared layouts.
Beneath the root, individual route files establish their own path contracts via createFileRoute and handle loaders, pending states, server handlers, and rendering.
This setup stays very close to standard React and build-tool mental models. Local development runs entirely through Vite or Rsbuild, route files are ordinary TypeScript modules, and full-stack behavior is layered in via Start primitives rather than an entirely separate "pages" system.
Link to headingFile routes carry types
File-based routing in Start uses filenames for paths. Each route file exports a typed route object.
import { createFileRoute } from '@tanstack/react-router'export const Route = createFileRoute('/products/$productId')({ component: ProductPage,})function ProductPage() { const { productId } = Route.useParams() return <ProductDetail productId={productId} />}The route knows that $productId exists because the route path and generated tree know it. That same pattern applies to search params when you add validation:
import { createFileRoute } from '@tanstack/react-router'import { z } from 'zod'const orderSearch = z.object({ status: z.enum(['open', 'closed', 'all']).default('open'), page: z.number().int().min(1).default(1), sort: z.enum(['newest', 'oldest']).default('newest'),})export const Route = createFileRoute('/orders')({ validateSearch: orderSearch, component: OrdersPage,})function OrdersPage() { const search = Route.useSearch() return <OrdersTable filters={search} />}For dashboards, admin panels, search UIs, and AI evaluation tools, the URL often carries application state: filters, tabs, pagination, sort order, selected model, and date range. Start treats that state as typed input at the route boundary, rather than forcing every component to parse strings from URLSearchParams.
Link to headingLoaders make data explicit
Before a route renders, Start aggregates the data it needs. The router knows which route is being entered, which params and search values apply, and which loader dependencies changed, so it can start the work before the component tree needs the data.
import { createFileRoute } from '@tanstack/react-router'import { z } from 'zod'const orderSearch = z.object({ status: z.enum(['open', 'closed', 'all']).default('open'), page: z.number().int().min(1).default(1),})export const Route = createFileRoute('/orders')({ validateSearch: orderSearch, loaderDeps: ({ search }) => ({ status: search.status, page: search.page, }), loader: ({ deps, abortController }) => fetchOrders(deps, { signal: abortController.signal }), component: OrdersPage,})function OrdersPage() { const orders = Route.useLoaderData() return <OrdersTable orders={orders} />}loaderDeps tells the router which search values should re-key the load. The abortController lets the route cancel stale work when navigation changes. On initial render, the loader can feed the HTML response; on client navigation, the router can reuse cached loader data or reload it based on staleness.
That is a different data model from colocating every read inside a server component. Start makes the route the unit of data orchestration, which is especially useful when the URL is the source of truth.
Link to headingServer functions are explicit
Server functions cross into server-only work through a typed bridge. You write them with createServerFn, validate the input, and call them from loaders, components, hooks, or other server functions.
import { createServerFn } from '@tanstack/react-start'import { z } from 'zod'const input = z.object({ status: z.enum(['open', 'closed', 'all']), page: z.number().int().min(1),})export const getOrders = createServerFn({ method: 'GET' }) .validator(input) .handler(async ({ data }) => { return fetchOrdersFromDatabase(data) })The server function owns the server capabilities: database access, environment variables, private API keys, filesystem access, or calls to third-party services. The route loader owns when that capability is needed for a route.
That split between capability and timing drives the cost and latency profile. If getOrders spends 80 ms on CPU-bound data transformations and 900 ms waiting on a database, an LLM provider, or a payments API, the request is mostly I/O-bound.
On Vercel, that server work runs on Vercel Functions with Fluid compute by default for TanStack Start projects. With Active CPU, CPU billing pauses while the function waits on external services, including database queries and AI model calls, while memory is billed for the time the invocation is in progress.
For AI-heavy routes, the same model applies. A server function can use the AI SDK with AI Gateway, stream the result, and spend most of the request waiting on provider I/O rather than burning CPU.
Link to headingSSR streams the document
Start supports full-document SSR, so the server renders the HTML document instead of a fragment under a client root. The root route includes the document shell because <HeadContent /> and <Scripts /> belong to the route tree.
Streaming allows the server to begin its response before all asynchronous dependencies resolve. In practice, this means the stable architecture of a route reaches the browser instantly, while slower data boundaries load concurrently. Users see useful HTML earlier, and the client progressively hydrates the page as the relevant JavaScript chunks arrive.
This model pairs well with server functions because backend delays are almost exclusively I/O-bound. A loader can safely await a database query, an LLM generation, or an upstream API, while Fluid compute provides a regional runtime optimized for high concurrency. To support long-running tasks, Fluid compute provides a default 300-second execution window. Hobby accounts are capped at this baseline, while Pro and Enterprise workspaces can explicitly configure durations up to 800 seconds.
Where each piece of server work runs depends on whether the response waits on it. Keep request-time server functions for work that belongs in the response, and move durable background work to Vercel Workflows when the user does not need to wait for it.
Link to headingDeferred hydration controls startup work
By default, Start hydrates the full document. That is the safest behavior, but large pages often include sections that should be visible and indexable before they become interactive.
Deferred hydration (which is currently experimental) adds a boundary for that case. You wrap part of the page in Hydrate, then choose when the boundary becomes interactive.
import { Hydrate } from '@tanstack/react-start'import { visible } from '@tanstack/react-start/hydration'export function ProductPage() { return ( <Hydrate when={visible({ rootMargin: '400px' })}> <Reviews /> </Hydrate> )}The server HTML stays in the document, so the browser displays it immediately. With visible({ rootMargin: '400px' }), Start holds off hydrating Reviews until the boundary comes within 400px of the viewport, then loads its split-off chunk and hydrates it.
React's selective hydration prioritizes work that React already needs to hydrate. Deferred hydration changes whether a subtree enters that queue at all.
Link to headingVercel deploys Start directly
Vercel does not require a TanStack Start app to become a Next.js app. TanStack Start on Vercel supports it via Nitro, so deployment is mainly about setting the runtime target rather than changing your app's architecture.
The production flow stays familiar:
npm install
npm run build
vercel deploy
Git deployments work the same way as other Vercel projects: each commit can generate a Preview Deployment, production deploys are immutable artifacts, and rollbacks shift traffic back to a previous artifact instead of changing the one that's running.
At runtime, Start apps use Vercel Functions and Fluid compute by default. Route loaders and server functions become server execution units that scale with traffic, run close to data when configured regionally, and use the same Active CPU pricing model that fits I/O-heavy workloads.
Link to headingA framework comparison
TanStack Start, Next.js App Router, Remix, and Astro all support server rendering, but they make different choices about where data loading lives and how much type information crosses the route boundary.
Each framework is strongest when its defaults match the application's shape.
Next.js App Router fits a server-driven architecture with integrated caching. Remix is built around web-standard fetch requests and progressive enhancement. Astro is strongest for content-heavy sites where interactivity can be isolated into islands. TanStack Start is a strong choice for applications driven by complex client-side state and URL structures. It infers types end-to-end, from route parameters and search state through loader data and server function inputs, so an interactive full-stack app stays typed without giving up explicit control over the server boundary.
Link to headingWhen TanStack Start is the right choice for your application
Start is a strong fit when your application has a meaningful client-side state model and a meaningful server boundary.
That usually includes internal tools, product dashboards, admin surfaces, search-heavy applications, AI evaluation interfaces, commerce operations tools, and apps where filters, sorting, pagination, and selected entities are part of the URL. Those routes benefit from typed search params, route-scoped loaders, and explicit server functions.
Start is less automatic for content-heavy sites that depend on mature React Server Components patterns, integrated image optimization, or ISR-first publishing flows. Those workloads often fit Next.js App Router better, especially when content rendering is the product surface.
Many teams have both shapes. The public site can live in one framework, and the authenticated app in another, or a single framework can host both with clear boundaries. Vercel's role is to remove the operational drag from that decision. You can deploy the framework that fits the surface, get a Preview Deployment for every commit, and run the server work on the same production platform.
If the hardest parts of your app are URL state, loader state, and the server boundary, TanStack Start keeps those concerns type-safe in one place rather than spreading them across the stack. If the hardest parts are content rendering and publishing, start with Next.js. Either way, you can deploy on Vercel, so you can pick the framework that fits what you're building without switching deployment platforms.
Start a new project on Vercel to deploy a TanStack Start or Next.js app.
Link to headingFAQ
Link to headingWhat is TanStack Start?
TanStack Start is a full-stack React framework built around TanStack Router. It adds the framework pieces around the router, including server entry, client entry, full-document SSR, streaming, route loaders, server functions, and deployment targets.
The important design choice is to keep the route tree central, so URL state and server calls remain defined together rather than scattered across the component tree.
Link to headingHow is TanStack Start different from Next.js App Router?
Next.js App Router is server-centric by default. React Server Components, framework caching, route handlers, Server Functions, and ISR are integrated into the application model.
TanStack Start is route-first and operates as a traditional SPA by default. Next.js and React Router both support SPA modes; the difference is that this is the default in TanStack Start, not an opt-in. Server execution is explicit through route loaders and createServerFn, and the router carries type information for params, search state, loader data, and route hooks.
Neither model is better in the abstract. Next.js fits content-heavy and server-component-heavy applications well. TanStack Start is well-suited to interactive apps where typed URL state and client-side application state are load-bearing.
Link to headingCan TanStack Start deploy to Vercel?
Vercel supports TanStack Start via Nitro, with Vercel Functions and Fluid compute as runtimes. Git deployments can produce Preview Deployments per commit, production deploys are immutable artifacts, and server functions can use Active CPU pricing for I/O-heavy work.
Ultimately, this decouples your choice of framework from your deployment target. A TanStack Start app can keep its route-first architecture while running on the same platform primitives Vercel provides for full-stack web applications.