Web Development

TanStack Start in Production: The Router Is the Framework, and That Changes Your Estimates

TanStack Start has been on its 1.x line since early 2025 and now pulls roughly a third of Next.js's weekly npm traffic. We shipped three client apps on it this year. The reason to pick it isn't speed — it's where your type errors show up, and how early.

TTulsiSenior Engineer
7 min read
Isometric illustration of a dense grid of server racks beside an open connected lattice, linked by thin lines

Three client apps this year shipped on TanStack Start instead of Next.js. A fourth started there and moved back after eleven days. That fourth one is the useful data point, because the thing that sent us back had nothing to do with performance, bundle size, or any of the benchmarks people argue about.

TanStack Start reached its 1.x line in February 2025 and sits at 1.168 as of this writing, pulling around 18 million weekly npm downloads against Next.js's 53 million. That's roughly one to three — no longer a curiosity, not yet a default. Which means the interesting question isn't "is it ready" but "what kind of app is it actually better at," and that answer is narrower and sharper than the comparison posts suggest.

The inversion nobody warns you about

Next.js starts on the server and lets you opt into the client. Every component is a Server Component until you write "use client", and the framework's whole design pressure pushes work toward the request boundary. TanStack Start starts on the client and lets you opt into the server. You write a React app, and where you need the server you reach for a server function.

This sounds like a preference. It isn't. It decides which mistakes are cheap and which are expensive. In Next.js, accidentally shipping a secret to the client is a real hazard and the framework spends enormous effort preventing it. In TanStack Start, the hazard is the opposite — accidentally doing on the client what should have been a single server round-trip, and only noticing on a slow connection in QA.

The router is where this shows up first. In Start, a route isn't a file that exports a default component. It's an object that declares its own search-param schema, its loader, and its error boundary, and the type of all three flows outward into every component that touches that route.

src/routes/dashboard.invoices.tsx
1import { createFileRoute } from "@tanstack/react-router";
2import { z } from "zod";
3 
4// The search schema is part of the route, not parsed inside a component.
5// Get this wrong and the build fails — not a runtime 500 in production.
6const searchSchema = z.object({
7  status: z.enum(["open", "paid", "void"]).catch("open"),
8  page: z.number().int().min(1).catch(1),
9});
10 
11export const Route = createFileRoute("/dashboard/invoices")({
12  validateSearch: searchSchema,
13  loaderDeps: ({ search }) => ({ status: search.status, page: search.page }),
14  loader: ({ deps }) => listInvoices(deps),
15  component: InvoicesPage,
16});

Compare that to how most Next.js codebases handle the same thing: read searchParams, coerce a string to a number, hope nobody links to ?page=abc. We have fixed that exact bug in production for three different clients. In Start it isn't a bug you can write — the schema is the route, and a bad link falls back to page 1 because the schema said so.

Type safety stops being a feature and becomes the constraint

Every framework claims type safety. What Start actually does is make the boundary typed in both directions, so a server function's argument type and return type are the same objects your components consume — no generated client, no manual DTO, no drift.

src/server/invoices.ts
1import { createServerFn } from "@tanstack/react-start";
2 
3export const listInvoices = createServerFn({ method: "GET" })
4  .validator(searchSchema)
5  .handler(async ({ data }) => {
6    // Runs only on the server. The import below never reaches the bundle.
7    const rows = await db.invoice.findMany({
8      where: { status: data.status },
9      skip: (data.page - 1) * 25,
10      take: 25,
11    });
12    return { rows, page: data.page };
13  });

The payoff is boring and enormous: rename a column, and TypeScript walks you through every component that read it. On the invoicing app we renamed a status enum value in week six. Twenty-two type errors, all real, all fixed in forty minutes. The equivalent change on an older Next.js project the same quarter took most of a day, because half the consumers were reading loosely-typed JSON from route handlers.

  • Links are typed — a wrong route path or a missing search param is a compile error, not a 404 someone finds in staging.
  • Loaders are cached and dedupedby the same query layer you already use, so the “fetch waterfall on navigation” problem mostly stops being yours to solve.
  • Search params are real state — filters, pagination and sort survive refresh and back-button by default, which quietly deletes a category of bug reports from dashboard work.

Where we stopped and went back to Next.js

The fourth project was a content site with a commerce section — roughly 400 mostly-static pages, heavy on SEO, light on interactivity. We started it in Start because the team had just finished the invoicing app and was fluent. Eleven days in we moved it to Next.js and lost about three days of work doing so.

Nothing broke. The problem was that we kept rebuilding things Next.js ships: incremental regeneration for the catalogue, an image pipeline that someone else maintains, per-route metadata that the crawler ecosystem already understands. Start's client-first model gave us nothing on pages that have almost no client, and we were paying for that model in setup we had to own.

The honest trade

TanStack Start's ecosystem is smaller, and on a content-heavy site that gap is where your budget goes. Next.js's advantage in 2026 is not the framework — it's that ten years of Vercel-adjacent tooling assumes it. If your app's hard part is rendering pages fast for crawlers, that assumption is worth more than any type-safety win.

The four-day migration, honestly accounted

The one migration that did go well was a Next.js Pages Router admin panel — internal, authenticated, zero SEO surface, about 60 screens. Four working days, two developers. Here's where the time actually went, because it wasn't where we estimated.

Same 60 screens, two different shapes

PAGES ROUTERfilterspaginationsortauthinvoices.tsxgetServerSidePropsone entry, untypedTANSTACK START/dashboard/invoicesRoute objectvalidateSearchzod schemaloaderDepstyped depsloaderserver fncomponenttyped props
Left: everything funnelled through one data-fetching entry per page, with nothing typed. Right: the same concerns split across a route tree, each part typed and independently testable. Most of the migration cost was in that split, not in the routing.

Half a day on the router, which surprised us — file-based routing maps over almost directly. Two and a half days on data fetching, which did not: every getServerSideProps became a server function plus a loader, and each one forced a decision we had previously avoided about what that screen actually needs. The last day was auth middleware and the build pipeline. The honest read is that most of the cost wasn't migration, it was paying down ambiguity the old code let us keep.

Our rule for picking

We stopped arguing about this by writing down one question: what is the hard part of this app? Not the biggest part — the part that will generate the most bugs in month four.

  • Hard part is application state — dashboards, admin panels, multi-step tools, anything with filters and tables and deep links. TanStack Start, without hesitation.
  • Hard part is pages and crawlers — marketing sites, catalogues, docs, publishing. Next.js. The ecosystem does the work for you.
  • Hard part is hiring — a client team of eight who will maintain it after us, none of whom have seen Start. Next.js, and we say so out loud in the proposal rather than quietly picking the fun option.

If you only change one thing

Don't migrate anything. Take the next internal tool you build — the one with a table, three filters and a detail drawer — and build it in TanStack Start. That shape is where the framework's argument is strongest and where being wrong costs you nothing. You'll know inside a week whether the type-safety-as-constraint model fits how your team works, and that's a far better basis for the decision than any comparison table, including this one.

Keep Reading

More from the blog

The Engineering Partner You Can Build On

Reliable software takes an experienced team that owns delivery end to end. Here’s the track record behind ours.

11+

Years Building Custom Software

320+

Projects Delivered Across Web, Mobile & AI

85%

Repeat Client Rate

12+

Countries Served Worldwide

Trusted by startups and enterprises worldwide

Work With Us

Let’s create
with purpose

Share your goals, timeline, and challenges — we’ll respond with clarity and next steps.

Ambitious ideas deserve thoughtful execution. Start the conversation and let’s define what success looks like.

Team

Acetrum

Est. 2015

4.9/5

Trusted by
top brands

Services interested in:

By submitting, I confirm I’ve read and agree with Privacy and Cookie Policies.

Newsletter

Signals worth
paying attention

No recycled headlines — just the patterns we’re seeing across real client work, distilled into one read a month.

A curated digest of practical thinking and real-world brand perspectives monthly.

No spam. Unsubscribe anytime.