Skip to main content
6a98af55ade40dc50672
·4 min read

This website has been migrated to Tanstack Router from Nextjs

After years with Next.js, I migrated my personal website. Here is what I found. Why I Moved From Next.js to TanStack Router.

react
nextjs
tanstack
tanstack start
typescript
4d5fd787ac74e0caa4f7

Sohan R. Emon

Developer, Learner, Tech Enthusiast

Why I Moved From Next.js to TanStack Router

After years building with Next.js, I migrated my personal portfolio to TanStack Router (via TanStack Start). This is not a tutorial. I just want to share what I found.

TLDR;

Next.js gave me a lot out of the box. But over time, I felt like I was fighting the framework more than using it. TanStack Router felt like going back to plain React routing, but with server capabilities I actually needed.

What Changed

Typesafe routes. In Next.js, route params are just strings. TanStack Router validates them at build time. If a route segment changes, your code breaks immediately - not at runtime.

tsx
// TanStack Router knows exactly what params exist
const articleRoute = createFileRoute('/article/$slug')()

// TypeScript knows this is a string
const slug = useParams({ from: '/article/$slug' }).slug

Search params are first-class. Need validated query strings? TanStack Router treats search params like everything else - typed, validated, and immutable.

tsx
// Define search params schema
const route = createFileRoute('/articles')({
  validateSearch: zod({
    page: z.number().default(1),
    limit: z.number().default(10),
  }),
})

// Fully typed, no parsing needed
const { page, limit } = useSearch({ from: '/articles' })

Loaders have built-in caching. TanStack Router caches loader data automatically. It's the same pattern as TanStack Query but for route data.

tsx
const route = createFileRoute('/articles')({
  loader: async ({ context }) => {
    // Cached by default, stale-while-revalidate works out of the box
    return context.queryClient.fetchQuery({
      queryKey: ['articles'],
      queryFn: () => fetchArticles(),
    })
  },
})

No more "use client" boundaries. This was the biggest mental load. In Next.js, every component is a Server Component by default, which means no state, no effects, no events. With TanStack Router, components are just components.

tsx
// This is just React. No directives needed.
function ArticlePage() {
  const [likes, setLikes] = useState(0)
  const data = useLoader({ from: '/article/$slug' })

  return <button onClick={() => setLikes(l => l + 1)}>{likes} likes</button>
}

Vite changes everything. Using TanStack Start (built on Vite), my dev server starts in 2-3 seconds instead of 10-12. HMR is near-instant.

Server functions are cleaner. Next.js Server Actions felt like a separate API. TanStack Router treats them as regular functions with full type safety.

tsx
// Server function - just a function
async function deleteArticle(id: string) {
  await db.article.delete(id)
}

// Call it from anywhere - client, loader, or another server function
const result = await deleteArticle(articleId)

No vendor lock-in. Using TanStack Start means I can deploy to Vercel, Netlify, Cloudflare, or run it locally with Bun. The build output is just a standard Nitro server.

Bundle size matters. TanStack Router adds roughly 10kb to your bundle. Compare that to the Next.js framework weight.

Less magic, more transparency. TanStack Start has most of the features I wanted from Next.js, but with way less hidden behavior. Once you set up SSR, you can entirely forget you're using a framework.

What I Gave Up

I lost some convenience. Next.js has built-in image optimization and tight Vercel integration. These are real features that work well.

I also gave up the ecosystem. Next.js has far more documentation, plugins, and answers on Stack Overflow. TanStack Router is newer and smaller.

The memory usage was also a factor. Next.js processes tend to grow in size during development. Vite stays light.

Why It Works For Me

Next.js lost me with constant API changes and complexity. The benefits do not justify the cognitive overload needed to remember all the gotchas. With TanStack Start, I set up SSR once and then just write React.

One practical detail: commit your routeTree.gen.ts file. It is generated code, but it enables all that type safety. Think of it as a type definition file that lives in your repo.

Who This Is For

This is not a "Next.js is dead" post. It's still the best choice for content-heavy sites with thousands of pages.

But if you're building a dynamic application, if you value type safety, if you want to understand what your code does without reading framework docs, TanStack Router (via TanStack Start) is worth a look.

The key question is: do you want the framework to make decisions for you, or do you want to make them yourself?

I chose the latter.

Found this useful?