Next.js vs TanStack Start vs Furin
Choosing a React meta-framework is not about picking the "best" one β it is about picking the one whose trade-offs match your constraints. This page compares Next.js (App Router), TanStack Start, and Furin across every dimension that matters for production decisions.
Disclaimers
- Next.js = App Router (stable), not Pages Router.
- TanStack Start = RC (1.x, pre-GA). The official TanStack site labels it "RC / preparing for 1.0". Server functions are stable; RSC is experimental and excluded.
- Furin = latest stable. Where a feature is planned but not shipped, we say so explicitly.
- This comparison is maintained by the Furin team. We flag our own limitations with β οΈ and our advantages with π‘ so you can verify independently.
At a glance
| Next.js | TanStack Start | Furin | |
|---|---|---|---|
| Runtime | Node.js | Node.js | Bun |
| HTTP server | Custom (Node) | Vite (dev) / H3 + srvx (prod) | Elysia |
| Bundler | Turbopack / webpack | Vite | Bun.build |
| Process model | Separate dev server + client bundler | Vite (dev) / H3 via srvx (prod) | Single process |
| SSG | β | β | β |
| ISR | β | β | β |
| SSR | β | β | β |
| Streaming | β | β | β |
| RSC | β | β οΈ Experimental | β |
| PPR | β οΈ Experimental | β | β |
| Binary compile | β | β | β |
| ISR in dev | β | β | β |
Architecture & Runtime
| Dimension | Next.js | TanStack Start | Furin |
|---|---|---|---|
| Runtime target | Node.js 18+ | Node.js 18+ | Bun only |
| HTTP framework | Custom server (minimal abstraction over Node http) | Vite (dev) / H3 (prod, via UnJS) | Elysia (typed, chainable, WinterCG) |
| Process model (dev) | 2 processes: Next.js dev server + Turbopack client bundler | 1 process: Vite dev server with SSR middleware | 1 process: Bun serves HTTP + bundles client via Bun.build + plugin pipeline |
| Process model (prod) | 1 process (custom server) or serverless | 1 process (H3/srvx) or serverless | 1 process (Elysia plugin) or compiled binary |
| WinterCG compliance | Partial (uses Node APIs) | Partial (H3 adapts via srvx) | Native (Elysia is WinterCG-native) |
| API routes colocation | β
Route handlers in app/api | β File-based API routes (H3 server functions) | β Elysia plugins colocated with pages |
| WebSocket / SSE | Custom implementation | Via H3 event handlers (crossws) | Native Elysia (.ws(), SSE via ReadableStream) |
π‘ Furin advantage β Single-process dev eliminates the "two servers out of sync" class of bugs. HMR, SSR, and API routes share the same Bun runtime and module graph.
β οΈ Furin limitation β Bun-only means you cannot deploy to standard Node.js containers without switching runtimes. Docker with
oven/bunis the recommended path.
Build System
| Dimension | Next.js | TanStack Start | Furin |
|---|---|---|---|
| Build tool | Turbopack (dev) / webpack (prod) | Vite | Bun.build |
| Compilation to binary | β | β | β
(compile: "embed") |
| Output directory | .next/ | .output/ or .tanstack-start/ | dist/bun/ |
| Client assets | Hashed chunks in .next/static | Hashed chunks in dist/client | Hashed chunks in dist/bun/client |
| Plugin API | Turbopack plugins (unstable) / webpack | Vite plugins (Rollup-compatible) | Bun plugins (native + JS API) |
| Tree-shaking | β (webpack) / β οΈ Turbopack WIP | β (Rollup) | β (Bun bundler) |
| Minification | SWC | Esbuild / Rollup terser | Bun built-in |
| Source maps | β | β | β (linked) |
| CSS handling | PostCSS / Tailwind via postcss.config.js | PostCSS / Tailwind via Vite | Tailwind v4 via bun-plugin-tailwind |
| Build time (cold) | Medium | Fast | Fast |
| Static export | β
output: "export" | β | β |
| Multi-target builds | β (Vercel-centric) | β οΈ Adapter-based | β
(bun, static, planned: vercel, cloudflare) |
π‘ Furin advantage β
compile: "embed"produces a single self-contained binary with all assets baked in. This is unique among React meta-frameworks and ideal for CLI tools, desktop apps, or air-gapped deployments.β οΈ Furin limitation β The Bun bundler ecosystem is younger than Vite/Rollup. Some advanced transforms may require writing a custom
BunPlugin.
Render Modes
| Dimension | Next.js | TanStack Start | Furin |
|---|---|---|---|
| SSR | β | β | β |
| SSG | β | β | β |
| ISR | β
(revalidate, revalidatePath) | β (caching via staleTime + TanStack Query only) | β
(revalidate on route, revalidatePath()) |
| Streaming SSR | β
(React 18 renderToReadableStream) | β | β
(React 18 renderToReadableStream) |
| RSC (React Server Components) | β | β οΈ Experimental | β |
| PPR (Partial Prerendering) | β οΈ Experimental | β | β |
| Server Actions | β | β
(createServerFn()) | β |
| Suspense boundaries | β | β | β |
| Error boundaries | β
(error.js) | β (React ErrorBoundary) | β
(error.tsx per segment) |
| Not-found handling | β
(not-found.js) | β (route fallback) | β
(not-found.tsx per segment) |
π‘ Furin advantage β ISR is implemented with
stale-while-revalidatesemantics, ETags, andCache-Tagheaders out of the box. No platform-specific adapter required.β οΈ Furin limitation β No RSC means every component in the tree is shipped to the client. For apps with heavy server-only logic (data transforms, heavy libraries), this increases bundle size compared to Next.js App Router.
Dev DX (Developer Experience)
| Dimension | Next.js | TanStack Start | Furin |
|---|---|---|---|
| HMR speed | Fast (Turbopack) | Fast (Vite) | Fast (Bun HMR + plugin pipeline) |
| SSR during HMR | β | β | β |
| ISR in dev | β (dev = SSR always) | β | β (planned) |
| Cache hit/miss visibility | NEXT_PRIVATE_DEBUG_CACHE=1 | Console logs | Built-in logs ([furin] ISR miss / hit / stale) |
Startup time (bun dev / next dev) | Medium | Fast | Fast (single process, no Vite server to boot) |
| Type-safe links | β (string-based href) | β
(<Link to="/about">) | β
(<Link to="/about"> with autocomplete) |
| Typed params / query | β οΈ Zod manual | β
(validateSearch, params) | β
(createRoute({ params: t.Object(...) })) |
| Error overlay | β (Turbopack) | β (Vite) | β (Bun runtime errors) |
| Source maps in dev | β | β | β |
| Log streaming | next-log or custom | Custom | evlog structured logging (built-in) |
π‘ Furin advantage β Built-in structured logs for every cache hit/miss/stale make debugging caching behavior straightforward in both dev and production.
β οΈ Furin limitation β The dev error overlay is less polished than Turbopack or Vite. Stack traces are accurate but formatting is basic.
Caching
| Dimension | Next.js | TanStack Start | Furin |
|---|---|---|---|
| Page cache (SSG) | Filesystem (.next/cache) | In-memory or custom | In-memory LRU (ssgCache) |
| Page cache (ISR) | Filesystem (.next/cache) | In-memory or custom | In-memory LRU (isrCache) |
| Data cache (loaders) | β
(fetch cache, unstable_cache) | β οΈ Manual (TanStack Query) | β (loaders re-run on every background revalidation) |
| Cache persistence | β Filesystem | β (in-memory only) | β (in-memory only) |
| Cache invalidation by path | β
(revalidatePath) | Manual | β
(revalidatePath(path, "page")) |
| Cache invalidation by layout | β
(revalidatePath(path, "layout")) | Manual | β
(revalidatePath(path, "layout")) |
| Cache invalidation by tag | β
(revalidateTag) | β | β (planned) |
| ETags | β | β | β
("buildId:timestamp") |
| Conditional requests (304) | β | β | β |
| CDN headers | s-maxage, stale-while-revalidate | Manual | Cache-Tag, s-maxage, stale-while-revalidate |
| CDN purger hook | Vercel-only | β | β
(setCachePurger()) |
| Client prefetch cache | Next.js router cache | TanStack Router cache | RouterProvider prefetch cache with auto-invalidation |
| Stale-deploy detection | β | β | β
(X-Furin-Build-ID header) |
π‘ Furin advantage β Built-in CDN semantics (
Cache-Tag,s-maxage,stale-while-revalidate,must-revalidate) without platform lock-in. ThesetCachePurger()hook works with any CDN (Cloudflare, Fastly, custom).β οΈ Furin limitation β No data cache means ISR background revalidations re-run all loaders from scratch, even if the underlying data has not changed. Next.js
unstable_cachecan avoid this. A data cache layer is planned.β οΈ Furin limitation β In-memory cache is lost on process restart. For Docker deployments, this means every deploy or crash wipes the ISR cache. A filesystem-backed cache adapter is planned.
Routing & Layouts
| Dimension | Next.js | TanStack Start | Furin |
|---|---|---|---|
| File-based routing | β
(app/) | β
(src/routes/) | β
(src/pages/) |
| Nested layouts | β
(layout.js) | β
(__layout.tsx) | β
(_route.tsx) |
| Dynamic segments | β
([id], [...slug]) | β
($id, $slug?) | β
([id], [...slug]) |
| Catch-all routes | β
([...slug]) | β
($.tsx) | β
([...slug]) |
| Route groups | β
((group)) | β | β |
| Parallel routes | β
(@team) | β | β |
| Intercepting routes | β
((.), (..)) | β | β |
| API routes colocation | β
(route.js) | β
(api/) | β (Elysia plugins) |
| Loading UI | β
(loading.js) | β (manual Suspense) | β (manual Suspense) |
π‘ Furin advantage β Layouts are Elysia routes with loaders, so you get typed params/query and middleware at every nesting level. The
_route.tsxfile is both a layout and a route boundary (error + not-found).β οΈ Furin limitation β No route groups, parallel routes, or intercepting routes. These are App Router-specific advanced patterns. If you need them, Next.js is the better choice.
Data Loading
| Dimension | Next.js | TanStack Start | Furin |
|---|---|---|---|
| Route loaders | β
(async function Page()) | β
(Route.loader) | β
(createRoute().loader()) |
| Layout loaders | β
(async function Layout()) | β
(Route.loader) | β
(_route.tsx loader) |
| Typed loader output | β οΈ (TypeScript inference, no runtime validation) | β
(Zod/Valibot via validateSearch) | β
(TypeBox via params/query on createRoute()) |
| Data cache intermΓ©diaire | β
(fetch cache, unstable_cache) | β οΈ (TanStack Query) | β (planned) |
| Mutations (server actions) | β
("use server") | β
(createServerFn()) | β (use API routes) |
| Redirect from loader | β
(redirect()) | β
(throw redirect()) | β
(throw redirect() or ctx.redirect()) |
| Not-found from loader | β
(notFound()) | β
(throw notFound()) | β
(notFound()) |
| Error handling | error.js boundaries | ErrorBoundary | error.tsx per segment |
| Streaming loader data | β (RSC streaming) | β
(Await + defer) | β
(renderToReadableStream + Suspense) |
π‘ Furin advantage β Loaders are standard async functions with TypeBox schemas for params/query. No magic conventions: what you see in
createRoute()is what gets validated at runtime.β οΈ Furin limitation β No server actions. Mutations must go through Elysia API routes (
/api/*) or external endpoints. This is explicit but more verbose than Next.js"use server".
Type Safety
| Dimension | Next.js | TanStack Start | Furin |
|---|---|---|---|
Typed links (href) | β (strings) | β
(to={"/about"}) | β
(to={"/about"} with autocomplete) |
| Typed search params | β (manual) | β
(validateSearch) | β
(query: t.Object(...)) |
| Typed route params | β (manual params: Promise<{ id: string }>) | β
(params) | β
(params: t.Object(...)) |
| Typed API routes | β (manual) | β οΈ (TanStack Router types, no native E2E API inference) | β (Elysia tRPC-like inference) |
| End-to-end type inference | β | β (TanStack Query + Router) | β οΈ (Router + client types, no Query equivalent) |
| Generated manifest | β | β
(routeTree.gen.ts) | β
(furin-env.d.ts) |
π‘ Furin advantage β
createRoute()is the single source of truth for params, query, and loader types. The generatedfurin-env.d.tsgives autocomplete on<Link>without any runtime overhead.β οΈ Furin limitation β No equivalent to TanStack Query for client-side data fetching. If you need complex client caching, deduplication, and background refetch, you must bring your own data library.
Deployment
| Dimension | Next.js | TanStack Start | Furin |
|---|---|---|---|
| Self-hosted (Docker) | β
(next start) | β (srvx/H3 server) | β
(bun run server.ts or compiled binary) |
| Static export | β
(output: "export") | β | β
(static target) |
| Vercel (serverless) | β (native) | β (via srvx adapters) | β (planned) |
| Cloudflare Workers | β οΈ (edge runtime) | β (official partner, native guide) | β (planned) |
| AWS Lambda | β οΈ (adapter) | β (via srvx/H3 adapters) | β (planned) |
| Edge runtime | β (Edge API Routes) | β οΈ (H3/srvx, adapter-based) | β οΈ (Elysia is WinterCG, but no edge adapter yet) |
| Embedded binary | β | β | β
(compile: "embed") |
| Single-file deploy | β | β | β (compiled binary ~20-50 MB) |
π‘ Furin advantage β The compiled binary (
compile: "embed") is a unique deployment artifact. One file, nonode_modules, no Docker image. Copy it to a server and run it. This is ideal for internal tools, offline environments, or simple VPS setups.β οΈ Furin limitation β Platform adapters (Vercel, Cloudflare) are not yet implemented. If you need serverless/edge deployment today, Next.js or TanStack Start are better choices.
When to choose which
Choose Furin if...
- You are already using Bun and want to stay in a single runtime.
- You want one process for dev and prod (no separate bundler + server).
- You need an embeddable binary (CLI tools, desktop apps, air-gapped deployments).
- You prefer Elysia as your HTTP framework (typed, chainable, fast).
- You want CDN-ready cache headers without platform lock-in.
Choose Next.js if...
- You deploy to Vercel (native integration, edge functions, analytics).
- You need React Server Components (zero client JS for server-only UI).
- You need Server Actions for mutations without API routes.
- You use advanced routing (parallel routes, intercepting routes, route groups).
- You need the largest ecosystem (plugins, examples, hiring pool).
Choose TanStack Start if...
- You want a library-first approach (bring your own UI, styling, state).
- You prefer Vite and the Rollup ecosystem.
- You need TanStack Router features (nested routing, search param validation, breadcrumbs).
- You want TanStack Query integration out of the box.
- You prefer H3/UnJS for server primitives.
Summary table
| You need... | Choose |
|---|---|
| RSC / Server Actions | Next.js |
| Vercel edge / serverless | Next.js |
| Embeddable binary | Furin |
| Bun-native single process | Furin |
| Vite + library-first | TanStack Start |
| TanStack Query + Router | TanStack Start |
| Maximum ecosystem maturity | Next.js |
| Maximum transparency / no magic | Furin |
Last verified: April 23, 2026. Sources: Next.js 15 docs, TanStack Start RC docs (v1.x, pre-GA), Furin source code, @tanstack/react-start GitHub (packages/react-start, start-server-core, start-plugin-core).