Choosing between TanStack Start and Next.js comes down to one question: do you want Server Components to be the default, or the exception? Next.js 16 starts every component on the server and makes you opt into interactivity. TanStack Start starts with ordinary hydrated React and lets you opt into server rendering, typed server functions and, experimentally, RSC. Pick by which default matches your app.
Everything else follows from that default, so here is what each framework actually ships as of September 2026.
What is the real difference between TanStack Start and Next.js?
Next.js App Router treats the server as the primary rendering environment. A component is a Server Component unless it carries "use client", which means no state, no effects and no event handlers until you cross that boundary. The Next.js 16.3 release (August 2026) leans further into this with Cache Components: 'use cache' marks parts of a tree as prerenderable, and partialPrefetching ships those shells to the client before a navigation. The team’s own phrasing is that the framework is heading back to “dynamic by default, with no hidden or implicit caching”, but the model is still server-first.
TanStack Start is a Vite (or Rsbuild) plugin on top of TanStack Router. Your route components render on the server, hydrate, and are interactive straight away. Server-only work lives in createServerFn calls and route loaders rather than in the component tree. The Start vs Next.js page in TanStack’s own docs puts it plainly: Start “defaults to interactive components (traditional React)” and you opt into Server Components where they earn their keep.
That has a practical consequence. A dashboard where nearly every component holds state fights Next’s default. A documentation or marketing site where nearly nothing does fights Start’s.
How do server functions compare with Server Actions?
Both frameworks let client code call server code. The shape of the boundary is where they diverge.
In Next.js a Server Function is a plain async function under a 'use server' directive. The use server reference shows the pattern:
'use server'
import { db } from '@/lib/db'
import { auth } from '@/lib/auth'
export async function createUser(data: { name: string; email: string }) {
const session = await auth()
if (!session?.user) throw new Error('Unauthorized')
const user = await db.user.create({ data })
return { id: user.id, name: user.name }
}
The TypeScript type on data is a promise to yourself, not a guarantee. Anything on the network can post anything to that endpoint, so the docs tell you to “validate inputs, check authentication and authorization” inside every function. There is no built-in validator step and no middleware chain; you compose those yourself, usually through a data access layer.
TanStack Start builds the validation and middleware steps into the function definition:
import { createServerFn, createMiddleware } from '@tanstack/react-start'
import { z } from 'zod'
const authMiddleware = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
const user = await getCurrentUser()
if (!user) throw new Error('Unauthorized')
return next({ context: { user } })
},
)
export const createUser = createServerFn({ method: 'POST' })
.middleware([authMiddleware])
.validator(z.object({ name: z.string().min(1), email: z.string().email() }))
.handler(async ({ data, context }) => {
const user = await db.user.create({ data: { ...data, ownerId: context.user.id } })
return { id: user.id, name: user.name }
})
// Anywhere in the app, including loaders:
await createUser({ data: { name: 'Ada', email: '[email protected]' } })
data in the handler is the output of the validator, so its type is real. The server functions guide also documents the pieces you would otherwise bolt on: throw redirect({ to: '/login' }) and throw notFound() from inside a function, setResponseHeaders for cache control, a .url property for progressive-enhancement forms, and a createCsrfMiddleware() that is installed automatically unless you define your own src/start.ts. Server functions are same-origin RPC by design; for a public endpoint you write a server route instead.
Neither approach is “safer” if you skip the auth check. Start just makes the check a reusable object you attach with .middleware([...]) rather than a line you remember to write.
How does caching differ?
This is where the two philosophies stop being abstract.
Next.js caches on the server. With cacheComponents: true, 'use cache' scopes are prerendered and stored, invalidated through revalidateTag and updateTag, and served as loading shells during navigation. We covered the keys and lifetimes in our post on the use cache directive. It is powerful, and it is a framework-specific model you have to learn.
TanStack Start caches on the client and at the CDN using things you already know. Loaders take staleTime and gcTime, the same stale-while-revalidate semantics as TanStack Query:
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => getPost({ data: { id: params.postId } }),
staleTime: 10_000,
gcTime: 5 * 60_000,
})
For HTML, the ISR guide is a prerender config plus Cache-Control headers with s-maxage and stale-while-revalidate, which any CDN honours. If you want the mechanics of those directives, our stale-while-revalidate write-up goes through them. There is no proprietary revalidation API because there is no proprietary cache.
The trade-off is honest in both directions. Next gives you fine-grained server-side invalidation that Start does not attempt. Start gives you a cache you can reason about with an HTTP header and a tab of DevTools.
Where can each one be deployed?
The Next.js deployment docs list Node.js and Docker as supporting every feature, static export as limited, and adapters as “varies”. As of the 16.3.4 docs, the only verified adapters are Vercel and Bun; Cloudflare and Netlify ship their own integrations that the Next.js team does not verify. Self-hosting works and is documented. Getting every feature working on a non-Vercel edge platform is still your problem.
TanStack Start has no adapters at all. Tanner Linsley wrote up the reasoning in Why TanStack Start is Ditching Adapters: the deployment target is a Vite plugin from the host, or Nitro for anything else. The hosting guide covers Cloudflare Workers through @cloudflare/vite-plugin with nodejs_compat, Netlify through @netlify/vite-plugin-tanstack-start, and a plain node .output/server/index.mjs for Railway, Docker or your own box. If you already deploy Astro or SvelteKit to Workers, Start feels the same.
For a UK studio shipping to Cloudflare, that matters. Start on Workers is the documented, partner-supported path; Next on Workers is a third-party integration.
How mature is TanStack Start?
Be careful here. TanStack Start reached its v1 Release Candidate on 23 September 2025, and npm has been publishing @tanstack/react-start 1.x ever since (1.168 at the time of writing). But the overview page in the docs still carries the Release Candidate note as of September 2026, and the comparison table describes it the same way. The API is treated as stable; the label has not moved.
React Server Components in Start are explicitly experimental. Enabling them means installing @vitejs/plugin-rsc and turning on rsc: { enabled: true } in the plugin config, and the returned components come back through server functions as data, not as the default rendering mode. In July 2026 TanStack published We Stopped Using RSC on TanStack.com, moving their own docs site back to regular SSR once their markdown and highlighting dependencies shrank. Read that as a signal about how central RSC is to Start’s roadmap.
Next.js, by contrast, is on 16.3 with RSC, Server Functions and Turbopack all stable, and its own 5.5x-faster-cached-builds and 22%-more-requests numbers published in the release notes. It also has eight years of Stack Overflow answers behind it.
What would I actually choose?
Start, for an application: an admin panel, a SaaS dashboard, anything where most components own state and where typed search params and loaders remove a whole category of bugs. Also Start for anything you intend to deploy to Cloudflare Workers, because the path is first-party.
Next.js, for a content-heavy or commerce site where Server Components genuinely cut client JavaScript, where you want 'use cache' and partial prefetching doing the heavy lifting, and where Vercel or a Node container is the target anyway. Also Next if the team already knows it and the project is a six-week build, because the ecosystem depth pays back faster than Start’s type safety.
The one thing I would not do is choose Start because “RSC is supported”. Choose it for the router, the server functions and the deployment story. If your architecture depends on Server Components as the default, that is Next’s game, and in September 2026 it still is.