After 4-5 years of building with Next.js, I've watched the framework evolve from a simple, predictable tool into something far more complex. Next.js remains incredibly powerful for the right use cases. But the constant mental model shifts have become exhausting, and I'm not alone in feeling this way.
When Next.js launched, it was genuinely revolutionary. Before that, building a production-ready React app meant orchestrating webpack, Babel, routing libraries, and countless other tools — each with their own configuration quirks. Next.js said, "Here's one thing. It handles routing, rendering, optimization, everything. Just use it."
The Pages Router was simple and predictable. File-based routing that made intuitive sense. API routes that felt natural. You didn't have to think about the framework — you just built your app. The mental model was consistent.
But at a certain point it went into the questionable direction...
A friendly warning: The article is subjective, expressing my personal feelings, but to make things fair I include other resources so you can form your own opinion. I believe in your independent judgement!
The App Router Era: Power and Complexity
Starting with Next.js 13, things became less stable. React Server Components (RSC) were introduced alongside the App Router, and the framework began changing its foundational assumptions frequently.
Suddenly, everything became "server-side by default." We entered a world of 'use client', 'use server', and the 'use cache' directive. The paradigm flipped entirely, bringing frequent hydration problems.
We adapted to the idea that everything was cached by default in Next.js 14. Then Next.js 15 arrived with Turbopack and a completely inverted mental model: nothing is cached by default. You now have to explicitly opt-in to caching behavior.
// Next.js 15 - Explicit caching with 'use cache' directive
'use cache'
export async function getData() {
const data = await fetch('/api/data')
return data
}
Next.js 15 made Turbopack the default (or at least heavily promoted) build tool, moving away from Webpack. The Rust-based bundler promised 10x performance improvements, but real-world data told a more nuanced story. As of 2025, Turbopack is still the direction, but developers report variable experiences — excelling at hot refresh but struggling with broken imports, high resources consumption, and cold starts in some scenarios.
The fact that Vercel published an official guide titled "Ten Common Mistakes with the Next.js App Router" speaks for itself.
The Reality Check
You're probably wondering: 'Does this mean we should ditch Next.js completely?' Absolutely not. Next.js remains excellent at what it does; it shines in specific scenarios but struggles to address what most modern web apps actually need.
Here's the thing: most applications aren't purely server-rendered. Most are a mix. A marketing homepage that needs SEO? Sure, server-render that. But then there's the dashboard, search functionality, user preferences, and interactive features — the stuff that doesn't need (or shouldn't have) server rendering.
With the Next.js App Router, you end up fighting the framework's server-first assumption. You're constantly adding 'use client' boundaries, managing server/client complexity, and dealing with performance trade-offs.
For projects that are truly content-heavy — blogs, documentation sites, e-commerce product catalogs — Next.js still makes total sense. But for the 70% of applications that are interactive with some server-side needs? The friction becomes harder to ignore.
TanStack Start Enters the Arena
That's when TanStack Start enters the picture: a framework built on stable patterns, client-first by design, and refreshingly explicit about what runs where. Here's what makes it different: TanStack has serious credibility. They've been shipping battle-tested tools that developers actually use for years.
- TanStack Query (formerly React Query) powers data fetching in millions of React applications worldwide
- TanStack Table powers countless data grids
- TanStack Router provides type-safe routing for developers who care about type safety
These are battle-tested tools with years of real-world usage, refined through community feedback, and stable APIs that don't flip every version (we can expect TanStack to change, but building blocks remain stable).
When TanStack decided to build a full-stack framework, there was already credibility, an existing philosophy, and a deep understanding of what developers actually need.
This image is quite self-descriptive, I think:

As of November 2025, it's a RC, with active development and growing community adoption. Unlike Next.js, the framework maintains consistency in its fundamentals. Out of curiosity, I built an app while it was still a beta, and now it is v1 already, and everything works without friction.
TanStack Start is built on two key technologies:
- TanStack Router (the entire routing layer with type safety)
- Vite (an industry-standard build tool)
This combination matters because each piece is proven, modular, and well-understood.
Core Philosophical Difference: Client-First vs Server-First
Next.js 15: Server-First Architecture
With the App Router, Next.js embraces a server-first paradigm. Every component is a React Server Component by default. You start on the server and explicitly opt into client-side interactivity with 'use client'.
This approach excels for content-heavy websites, blogs, and e-commerce product pages where SEO matters and users primarily consume content.
But for highly interactive applications — dashboards, admin panels, SaaS tools — this creates friction. Developers find themselves constantly fighting the framework's assumptions, marking files with 'use client', and navigating complex server/client boundaries.
TanStack Start: Client-First with Powerful Server Capabilities
TanStack Start takes a different approach: client-first with selective server-side rendering.
Routes are rendered on the server by default for the initial request (providing SSR benefits), but you have fine-grained control over the rendering mode via the ssr property on each route:
// TanStack Start - SSR configuration per route
// Pure client-side rendering (like a traditional SPA)
export const Route = createFileRoute('/dashboard')({
ssr: false,
component: DashboardComponent,
})
// Full SSR for SEO-critical pages
export const Route = createFileRoute('/products')({
ssr: true,
loader: async () => fetchProducts(),
component: ProductsComponent,
})
// Data-only SSR: fetch data server-side, render client-side
export const Route = createFileRoute('/admin')({
ssr: 'data-only',
loader: async () => fetchAdminData(),
component: AdminComponent,
})
Key clarification: TanStack Start's "client-first" philosophy means:
- The mental model is client-centric: You write code thinking about the client experience first
- SSR is opt-in per route: Unlike Next.js where you opt-out of server rendering, TanStack Start lets you opt-in where needed
- Code is isomorphic by default: Route loaders run on both server (initial load) and client (navigation)
This gives you the best of both worlds: SSR performance where it matters, with SPA-like navigation for everything else.
Feature-by-Feature Deep Dive
1. Routing with Type Safety
This is where TanStack Start truly shines. The framework generates a routeTree.gen.ts file containing complete type information about every route — a feature Next.js simply doesn't offer.
Next.js 15 Example
// app/products/[slug]/page.tsx
export default async function ProductPage({params}: { params: Promise<{ slug: string }> }) {
const {slug} = await params
// Use slug...
return <div>Product: {slug}</div>
}
// In a component - just strings, no type checking
<Link href={`/products/${productId}`}>
View Product
</Link>
TanStack Start Example
// routes/products.$id.tsx
export const Route = createFileRoute('/products/$id')({
loader: async ({params}) => {
// params.id is fully typed automatically
return getProduct(params.id)
},
component: ProductComponent,
})
function ProductComponent() {
const product = Route.useLoaderData() // Fully typed!
return <div>{product.name}</div>
}
// Navigation with compile-time safety
navigate({
to: '/products/$id',
params: {id: productId} // TypeScript validates this exists and is correct type
})
Change a route parameter? Every link using that route fails at build time — not at runtime. This eliminates an entire class of bugs before shipping.
Learn more in the TanStack Router Type Safety guide.
2. Data Fetching: Isomorphic Loaders vs Async Server Components
Next.js 15 Approach
// app/page.tsx - Async Server Component
export default async function Page() {
// Direct data fetching on the server
const res = await fetch('https://api.example.com/data')
const data = await res.json()
return (
<main>
<h1>{data.title}</h1>
<p>{data.description}</p>
</main>
)
}
// To cache data in Next.js 15, use 'use cache' directive
'use cache'
export async function getData() {
const data = await fetch('/api/data')
return data
}
See Next.js caching documentation.
TanStack Start Approach
export const Route = createFileRoute('/products/$id')({
loader: async ({params}) => {
// This loader is ISOMORPHIC:
// - Runs on server for initial load
// - Runs on client for subsequent navigation
const product = await getProduct(params.id)
const wishlist = await getWishlist()
return {product, wishlist}
},
component: ({useLoaderData}) => {
const {product, wishlist} = useLoaderData()
return (
<div>
<ProductCard product={product} />
<WishlistChecker product={product} wishlist={wishlist} />
</div>
)
}
})
These are called "isomorphic loaders" — the same code runs on server during initial load and on client during navigation. This is a fundamental architectural difference.
Here's the key advantage: TanStack Start integrates deeply with TanStack Query. You get automatic caching, stale-while-revalidate, and background refetching out of the box.
Navigate to /products/2, then back to /products/1? The data is still there. No refetch. Instant navigation. It's a cohesive system where data fetching, caching, and navigation work together seamlessly.
Learn about TanStack Start's execution model and isomorphic loaders.
3. Server Functions: Flexibility vs Convention
Next.js 15 Server Actions
// Server Action - tightly coupled to forms
'use server'
export async function createUser(formData: FormData) {
const name = formData.get('name')
const newUser = await db.users.create({
name: name as string
})
revalidatePath('/users')
return newUser
}
// Usage in component
<form action={createUser}>
<input name="name" />
<button type="submit">Create</button>
</form>
Server Actions are primarily designed for form submissions and only support POST requests by default. While this provides built-in CSRF protection (comparing Origin and Host headers), it also limits flexibility.
It gets even trickier with middleware, where the exploit helped to bypass a security check.
See Next.js Server Actions documentation and security considerations.
TanStack Start Server Functions
import {createServerFn} from '@tanstack/react-start'
import {z} from 'zod'
export const createUser = createServerFn({method: 'POST'})
.validator(z.object({
name: z.string().min(1),
email: z.email()
}))
.middleware([authMiddleware, loggingMiddleware])
.handler(async ({data, context}) => {
// data is validated and fully typed
return db.users.create(data)
})
// Call it from anywhere - not just forms
const mutation = useMutation({
mutationFn: createUser
})
<button onClick={() => mutation.mutate({name: 'Alice', email: 'alice@example.com'})}>
Create User
</button>
TanStack Start server functions support:
- Any HTTP method (GET, POST, PUT, DELETE, etc.)
- Built-in validation with Zod or other validators
- Composable middleware (authentication, logging, etc.)
- Client-side and server-side middleware execution
While it requires more code, it's far more functional and flexible.
Learn more in the TanStack Start Server Functions guide and Middleware guide.
4. SEO: Static Metadata vs Dynamic Head Management
Both frameworks handle SEO well, but with different approaches.
Next.js 15 Metadata API
import type { Metadata, ResolvingMetadata } from 'next'
type Props = {
params: Promise<{ id: string }>
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}
export async function generateMetadata(
{ params, searchParams }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
// read route params
const { id } = await params
// fetch data
const product = await fetch(`https://.../${id}`).then((res) => res.json())
// optionally access and extend (rather than replace) parent metadata
const previousImages = (await parent).openGraph?.images || []
return {
title: product.title,
openGraph: {
images: ['/some-specific-page-image.jpg', ...previousImages],
},
}
}
export default function Page({ params, searchParams }: Props) {}
Simple and consistent. Every page exports metadata, and Next.js handles it automatically. The trade-off? Quite verbose, requires additional function for dynamic routes and static in Server Components, less flexible for complex scenarios.
See Next.js Metadata API documentation.
TanStack Start Head Function
export const Route = createFileRoute('/blog/$slug')({
loader: async ({params}) => {
const article = await fetchArticle(params.slug)
return article
},
head: ({loaderData}) => ({
meta: [
{title: loaderData.title}, // Fully typed from loader!
{name: 'description', content: loaderData.excerpt},
{property: 'og:title', content: loaderData.title},
{property: 'og:description', content: loaderData.excerpt},
{property: 'og:image', content: loaderData.coverImage},
],
links: [
{rel: 'canonical', href: `https://example.com/blog/${loaderData.slug}`},
],
}),
component: BlogPostComponent,
})
The head function receives fully-typed loaderData, ensuring meta tags are never out of sync with your data. Child routes can override parent route meta tags intelligently, creating a composable head management system.
The Real Advantage: Selective SSR for SEO
You choose which routes need server-side rendering:
// Marketing page: Full SSR
export const Route = createFileRoute('/about')({
ssr: true,
loader: () => fetchAboutData(),
component: AboutPage,
})
// Internal dashboard: Pure client-side (no SEO needed)
export const Route = createFileRoute('/dashboard')({
ssr: false,
component: Dashboard,
})
// Blog: Static prerendering at build time
export const Route = createFileRoute('/blog/$slug')({
ssr: 'prerender',
loader: ({params}) => fetchBlogPost(params.slug),
component: BlogPost,
})
For applications that are primarily interactive dashboards with some public-facing content, this granular control is invaluable.
TanStack Start even supports static prerendering with intelligent link crawling:
// vite.config.ts
export default defineConfig({
plugins: [
tanstackStart({
prerender: {
enabled: true,
autoStaticPathsDiscovery: true,
crawlLinks: true,
concurrency: 14,
},
}),
],
})
The framework automatically crawls your site during build and prerenders all static pages. See the Static Prerendering documentation.
5. The Build Tool Story: Vite vs Turbopack
Next.js 15 Update: Turbopack was introduced as the new build tool (moving away from Webpack), though not yet the absolute default everywhere. Performance improvements are notable but variable depending on project complexity.
Turbopack Performance (Next.js 15 - 2025):
- Fast Refresh: Improved over Webpack but with variable performance in large monorepos
- Build speeds: Generally faster for medium projects, but struggles in very large codebases
- Cold starts: Still an area where some teams report slowness compared to Vite
TanStack Start uses Vite, which has been battle-tested for years across the ecosystem:
- Predictable performance across different project sizes
- Mature ecosystem with extensive plugin support
- No major surprises between versions
I will let you to decide which is better, but in my opinion Turbopack is not as matured as Vite or Webpack, and Vite has stronger positions in comparison with Webpack, so Vite is definitely a winner here.
Learn about Next.js 15 and Turbopack and Vite benchmarks.
6. Deployment: Vendor Lock-in vs True Flexibility
Next.js 15: Optimized for Vercel
Next.js is heavily optimized for Vercel deployment. Deploy to Vercel? Everything works magically.
Self-host? You're fighting against framework assumptions:
- Build artifacts need environment-specific configuration
- Image optimization and some performance features tied to Vercel infrastructure
- Feature parity issues across different hosting providers
My devOps colleague hated it when I used a Next.js middleware in our projects because... Have you ever tried to deploy Next.js apps on AWS? Challenging, to say the least.
Next.js is not build once, run anywhere. You often need to rebuild per environment.
While it's possible to deploy Next.js elsewhere, it requires significantly more configuration and often lacks feature parity with Vercel deployments.
TanStack Start: Deploy Anywhere
TanStack Start is built on Vite — no vendor lock-in, no environment assumptions.
Deploy to:
- Cloudflare Workers
- Netlify
- Your own Docker container
- AWS Lambda
- Any Node.js server
Configuration example for Cloudflare:
// vite.config.ts
import {defineConfig} from 'vite'
import {tanstackStart} from '@tanstack/react-start/plugin/vite'
import {cloudflare} from '@cloudflare/vite-plugin'
import viteReact from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
cloudflare({viteEnvironment: {name: 'ssr'}}),
tanstackStart(),
viteReact(),
],
})
The framework doesn't care where you deploy. Build artifacts are truly portable. You build once and run anywhere.
Check this short YouTube video to see how to deploy TanStack Start in less than a minute (it is indeed 1 minute, I tried myself!).
7. Developer Experience: Next.js 15 vs TanStack Start
From my experience, dev experience of Next.js 15 is rather poor. They have announced adequate hydration errors handling just recently. But TanStack Start went far and beyond.
Here is how it looks like starting with TanStack Start:
1) UI option
2) Console option
3) DevTools
And what we have with Next.js 15:
1) Console option
2) DevTools
Well, Next.js can definitely do better?
When to Choose Each Framework
Choose Next.js 15 if:
✅ Building content-heavy sites (blogs, marketing pages, documentation, e-commerce)
✅ SEO is mission-critical with zero-config needs
✅ Deploying to Vercel
✅ Team already knows Next.js thoroughly (sad, but true, learning curve can be a deal breaker if you have juniors in your team)
✅ App is mostly read-heavy with limited interactivity
Choose TanStack Start if:
✅ Building highly interactive applications (dashboards, admin tools, SaaS)
✅ Need deployment flexibility without vendor lock-in (to have some mercy on your devOps engineers)
✅ Type safety across your entire app is non-negotiable
✅ Already using TanStack Query, Table, or Form
✅ Want fine-grained control over SSR per route
You can check the full table here.
The End of the Monopoly
For years, Next.js was the only real choice for full-stack apps. One framework, one pattern, one way to build. While that simplicity helped the ecosystem grow, it also created constraints — not every application fits the server-first mold.
TanStack Start changes that equation. It's not trying to kill Next.js — it's offering developers a genuine alternative with a different philosophy. Client-first, modular, deployment-agnostic, and built on battle-tested libraries.
Next.js isn't going anywhere. It will continue dominating content-heavy sites where its server-first approach makes perfect sense. But TanStack Start brings real competition for interactive applications, and that competition makes the ecosystem healthier.
I've watched it evolve from simple and predictable to powerful but complex. TanStack Start looks promising precisely because it takes a different path — stability over constant reinvention, flexibility over convention, explicit control over implicit defaults.
The React ecosystem needed this. Not because Next.js is bad, but because having genuine alternatives — frameworks competing on merit and philosophy rather than inertia — benefits everyone.
Developers win when they have real choices, not just default options. And right now, TanStack Start is the most compelling alternative I've seen.
Additional Resources
Next.js 15
- Next.js 15 Release Blog
- Next.js App Router Documentation
- Next.js
use cacheDirective - Next.js Caching Guide
- Next.js Metadata & OG Images
- Common Mistakes with App Router
TanStack Start RC
- TanStack Start Official Documentation
- TanStack Start v1 Release Announcement
- TanStack Router Type Safety Guide
- TanStack Start Selective SSR
- TanStack Start Server Functions
- TanStack Start Middleware
- TanStack Start Hosting Options
- TanStack Start Static Prerendering
- TanStack Start Execution Model
- TanStack Start GitHub Releases
- Awesome demo video by Jack Herrington
Deployment Guides
- TanStack Start on Cloudflare Workers
- TanStack Start on Netlify
- Official Hosting documentation
- Deploy TanStack Start in Less Than A Minute








Top comments (0)