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.

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.