Why I Didn't Choose Next.js — The Decision Criteria for Picking TanStack Start
Go deeper on this topic
TanStack Start × Cloudflare
Full-stack setup with TanStack Start and Cloudflare Pages, plus Tailwind v4 migration
Article 1 of 4 in this series.
Next reads
TanStack Start x Cloudflare Pages — A Complete Record of Pitfalls and Solutions
Five landmines hit while deploying TanStack Start v1 to CF Pages — Vite plugin ordering, ServerFn bugs, FOUC prevention, and more — with workarounds shown in real code.
What Broke in the Tailwind CSS v4 Migration — @custom-variant and Dark Mode Implementation Notes
Breaking changes encountered migrating from Tailwind CSS v3 to v4, and implementation patterns for dark/light mode using @custom-variant.
IndexNow x Cloudflare Pages — Instant URL submissions from sitemap.xml
A practical workflow: treat sitemap.xml as the source of truth, then submit all URLs to IndexNow right after a Cloudflare Pages deploy to shorten discovery time for new content.
Use the topic hub as a map, then continue to the next article in the series. New here? Start at the home hub →
Introduction
In technology selection, articles about "what was chosen" are plentiful. But I believe "what wasn't chosen, and why" holds more value. Reasons for choosing can always be rationalized after the fact, but reasons for not choosing are where project-specific constraints and decision criteria are distilled.
In the technology selection for this site, Next.js was the leading candidate. It holds overwhelming market share as a React full-stack framework, has comprehensive documentation, and a mature ecosystem. There's no shortage of adoption cases, and "just go with Next.js" seems like a rational choice.
Yet I didn't choose Next.js. This article is a record of that decision. I don't want to criticize Next.js. I want to articulate as precisely as possible where this project's requirements and Next.js's design philosophy created mismatches.
Evaluating Next.js
As of 2026, Next.js is built around App Router + React Server Components (RSC). Server-side data fetching, streaming SSR, partial hydration -- all attractive features.
RSC in particular is a brilliant idea: separating UI into server and client components, and not sending server-completable parts to the client. Bundle size reduction and performance improvement that developers get without conscious effort. An ideal world.
However, examining this project's components one by one revealed mismatches where that ideal doesn't work well.
Mismatch 1: Vercel Dependency vs Edge-First
Next.js delivers maximum performance when running on Vercel. ISR (Incremental Static Regeneration), Edge Functions, Image Optimization, Analytics -- these features are tightly coupled with Vercel's infrastructure.
Of course, Next.js supports self-hosting, and adapters exist for running it on Cloudflare. But actually trying to deploy to Cloudflare Pages reveals constraints.
ISR doesn't work on Cloudflare. next/image optimization requires a custom loader outside Vercel. Middleware API behavior differs subtly between Vercel and other platforms. In other words, some of Next.js's strengths rest on the implicit assumption of "running on Vercel."
This project was premised on deploying to Cloudflare Pages from the start. The reason is simple: the number of edge locations and the cost-performance ratio. For a site hosting 211 interactive components, initial load speed directly impacts experience quality. I wanted instant responses from the edge, no matter where in the world users access from.
I also didn't want to be locked into a hosting provider. Having a framework choice constrain hosting choices felt like an unnatural dependency direction. Frameworks should be hosting-neutral.
Mismatch 2: Server Components vs Client-Heavy
This was the biggest mismatch.
This site has 211 interactive components. BubbleWrap, GravityBadges, ParticleField, FluidSimulation, PianoKeyboard -- all thoroughly client-side components leveraging Canvas API, Web Audio API, pointer events, and requestAnimationFrame.
RSC delivers maximum benefit for components that can be completed server-side, like fetching data from a database and displaying it. Blog post lists, user profiles, product catalogs -- RSC is tremendously effective for these.
However, on this site, content that can be completed server-side is surprisingly scarce. Profile information, skills list, social links -- that's about it. Over 95% of the total consists of client-side interactions, deriving virtually no benefit from RSC.
In Next.js's App Router, the default is Server Component, and components that use client-side features need the 'use client' directive. That would mean writing 'use client' on 211 interactive components.
It's not technically impossible. But when the framework's design philosophy is "server is default" while the vast majority of the project is "client is default," that's a philosophical mismatch. Fighting against a framework's design philosophy over the long term generates friction.
TanStack Start has no concept of this directive. All components are written as plain React components. When server-side processing is needed, ServerFn is used, but this project doesn't even use ServerFn (there was also a known compatibility issue with Cloudflare Pages, but it wasn't needed in the first place).
Mismatch 3: File-Based Routing Constraints
Both Next.js and TanStack Start use file-based routing, but with different design philosophies.
For this site, I wanted to classify 211 interactions into 6 categories (arcade, playground, toys, creative, music, tools) with individual pages for each. With Next.js, dynamic routes [slug]/page.tsx accomplish this. Same with TanStack Start.
The difference emerges in type safety. TanStack Router provides complete type inference for route parameters and search parameters.
// TanStack Router's type-safe route definition
export const Route = createFileRoute('/playground/$slug')({
component: PlaygroundPage,
head: ({ params }) => ({
// params.slug is inferred as string type
meta: [{ title: getComponentTitle(params.slug) }],
}),
})params.slug is inferred as string type, and attempting to access a non-existent parameter causes a compile error. The same applies to search parameters -- defining a Zod schema with validateSearch automatically infers query parameter types.
Type definitions for params are possible in Next.js too, but not with the same depth of type inference as TanStack Router. When managing routing type-safely across 211 pages, this difference is non-trivial.
The auto-generated route tree is also a plus. src/routeTree.gen.ts is generated automatically, centrally managing type information for all routes. Adding a new route file automatically updates the route tree. No manual route definitions needed.
Why TanStack Start Was Chosen
With the three mismatches in mind, here's a summary of why TanStack Start was chosen.
Vite-based. TanStack Start is built on Vite. Integration with Cloudflare Pages is complete just by adding @cloudflare/vite-plugin to the plugins (there was a plugin ordering trap, but that's covered in a separate article). Compared to Webpack-based Next.js, the build pipeline is simpler and more transparent.
Type-safe routing. As mentioned, the type inference for route and search parameters is excellent. For managing routing across 211 pages, type safety directly impacts both development speed and quality.
head() API. SEO, OGP, and JSON-LD management can be written declaratively. Head information for each route can be contained within its route file, preventing meta information from scattering.
head: ({ params }) => ({
meta: [
{ title: `${component.title} | sakimyto` },
{ name: 'description', content: component.description },
{ property: 'og:title', content: component.title },
],
links: [
{ rel: 'canonical', href: `https://sakimyto.com/playground/${params.slug}` },
],
scripts: [
{
type: 'application/ld+json',
children: JSON.stringify(generateJsonLd(component)),
},
],
})Bundle size. The framework itself is smaller than Next.js. Since the 211 interactive components already carry considerable bundle size, I wanted the framework layer to be as lightweight as possible.
The framework's philosophy aligned with the project's requirements. TanStack Start is "a client framework that can also do server-side," while Next.js is "a server framework that can also do client-side." Since this project's center of gravity is on the client side, the former was more natural.
Tradeoffs
There are tradeoffs, of course. I won't hide them.
Ecosystem maturity. Next.js's ecosystem is overwhelming. Authentication (NextAuth.js), CMS integrations, database ORMs -- countless tools optimized for Next.js exist. TanStack Start doesn't have that. You need to assemble what you need yourself.
Documentation quality. Next.js's documentation is systematic, covering everything from tutorials to API references. TanStack Start's documentation was still thin in many areas at v1 release. Integration with Cloudflare Pages in particular was almost entirely trial-and-error.
Willingness to solve "undocumented" issues yourself. The landmines I stepped on were not few. The Vite plugin ordering issue (tailwindcss -> tsConfigPaths -> cloudflare -> tanstackStart -> react), ServerFn's Cloudflare Pages incompatibility, HMR instability -- all resolved by reading GitHub Issues and source code.
Community size. When issues arise, searching Stack Overflow often yields no answers. GitHub Issues and Discussions become the primary information sources. There's no comfort of "everyone uses it."
These tradeoffs were acceptable for this project. No authentication, CMS integration, or database needed -- all data sits as static TypeScript constants in src/data/. I judged that the documentation gap could be covered by the ability to read source code.
That said, for team development or business-critical projects, this decision might change. The depth of ecosystem and community is insurance for long-term operation that shouldn't be ignored.
Summary: Next.js vs TanStack Start
I believe technology selection should be guided by the "center of gravity" of the project.
This project's center of gravity was clear: client-side interaction and edge deployment. The 211 interactive components are the site's essence, delivered worldwide from Cloudflare Pages' edge. There's almost no data that needs server-side processing.
Next.js's center of gravity is "server-side rendering" and the "Vercel platform." That's an excellent design philosophy, and the optimal choice for many projects. But it didn't align with this project's center of gravity.
TanStack Start's center of gravity is "lightweight Vite-based framework" and "type-safe routing." Client-side focused with server-side capabilities available as needed. It matched this project's center of gravity.
The point is not that Next.js is a bad framework. Every framework has a design philosophy, and whether that philosophy aligns with project requirements is the essence of technology selection.
"The most popular framework" isn't necessarily "the most suitable framework." Identify your project's center of gravity and choose a framework whose design philosophy aligns with it. Technology selection is, in the end, that kind of work.