All articles
Migration

From Next.js to TanStack Start: A Migration Guide

A practical, honest look at migrating a production Next.js App Router application to TanStack Start — what's better, what's harder, and what to expect.

Share:
Priya Sharma
Priya Sharma
April 14, 202611 min read

I recently migrated a mid-sized SaaS application from Next.js App Router to TanStack Start. This post documents what I learned — the wins, the gotchas, and the honest trade-offs.

TL;DR: TanStack Start wins on type safety, developer ergonomics, and bundle size. Next.js has a larger ecosystem and more hosting options. For a TypeScript-first team building a complex SPA, TanStack Start is the better choice.

The Application Context

The app I migrated:

  • 47 routes (including dynamic and nested)
  • Authentication with session-based auth
  • ~15 API endpoints (server functions)
  • Heavy use of React Query for data fetching
  • Deployed on Cloudflare Workers

This wasn't a toy app — it's a real SaaS with paying customers.

Mental Model Differences

Next.js: File = Route + Server Component + Data

Next.js blends routing, rendering, and data fetching into files. page.tsx becomes a React Server Component by default, and you opt into client with 'use client'. Data is fetched directly in components.

TanStack Start: Route = Config + Loader + Component

TanStack Start separates concerns more explicitly. Routes are defined with createFileRoute(), loaders handle data fetching, and components are always client components. Server functions (createServerFn()) handle server-side logic.

Neither approach is wrong — they're different mental models that suit different team preferences.

What Got Better

End-to-End Type Safety

This was the biggest win. In Next.js, I was constantly casting params from unknown and manually typing search parameters. In TanStack Start:

tsx
// Next.js — params are unknown, require casting
export default async function Page({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug) // params.slug could be anything
}

// TanStack Start — fully typed, validated at runtime
export const Route = createFileRoute('/blog/$slug')({
  loader: ({ params }) => getPost(params.slug), // params.slug: string, guaranteed
  component: BlogPost,
})

Across 47 routes, this eliminated an entire category of runtime errors.

React Query Integration

Next.js and React Query coexist awkwardly. Next.js wants to own data fetching; React Query also wants to own data fetching. You end up with workarounds like initialData hydration that are fragile and confusing.

TanStack Router + TanStack Query is designed to work together:

tsx
export const Route = createFileRoute('/users')({
  loader: ({ context }) =>
    // Prefetch in loader, React Query handles the rest
    context.queryClient.ensureQueryData(usersQueryOptions()),
  component: UsersPage,
})

function UsersPage() {
  // Data is already in cache from loader — no loading state on initial render
  const { data } = useQuery(usersQueryOptions())
}

Bundle Size

After migration, our JavaScript bundle dropped by 34%. Next.js App Router ships a significant runtime. TanStack Start is leaner.

What Got Harder

Image Optimization

next/image is genuinely excellent. TanStack Start has no equivalent. You'll need to handle image optimization yourself — either through Cloudflare Images, a CDN, or a library like unpic.

Incremental Static Regeneration

If your app relies heavily on ISR for performance, that pattern doesn't have a direct equivalent in TanStack Start. You'll need to rethink your caching strategy using CDN cache headers or a dedicated caching layer.

Ecosystem

Some Next.js-specific libraries (next-auth, next-intl, etc.) don't work out-of-the-box. You need to find or build equivalents. For us, the TanStack ecosystem was sufficient, but if you depend on Next.js-specific plugins, audit them before migrating.

The Migration Process

Here's the approach that worked for us:

Phase 1: Extract business logic Move all business logic, API calls, and data utilities to framework-agnostic files. This took about a week and paid dividends immediately — it forced us to clean up a lot of coupling that had crept in.

Phase 2: Rebuild routes Recreate routes one by one in TanStack Start. Start with the simplest routes (no loaders, no params) and work toward complexity. This took two weeks.

Phase 3: Server functions Migrate Next.js API routes and Server Actions to TanStack Start server functions. This was mostly mechanical.

Phase 4: Authentication Rebuild auth using our chosen auth library. This was the riskiest phase — budget extra time for testing.

Total migration time: 5 weeks for a 2-developer team.

Would I Do It Again?

Yes — for this use case. The type safety improvements alone have prevented multiple production bugs. The developer experience is genuinely better for our team's workflow.

If you're building a content-heavy site that relies on ISR and image optimization, Next.js remains the better choice. If you're building a complex TypeScript application with lots of routes and data interactions, TanStack Start is worth the migration cost.

Back to all articles
Previous

AI-Optimized Code Architecture

How to structure your React and TypeScript codebase so AI assistants like Claude Code, Copilot, and Cursor can generate accurate, idiomatic code on the first try.

Next

Free vs Premium Templates: Which Should You Choose?

A practical breakdown of when our free boilerplates are enough and when a premium template pays for itself — with a decision framework.