TanStack/Start vs Remix vs Next.js

Back to Blog Listing

TanStack/Start vs Remix vs Next.js

Why I have been using TanStack/Start instead of Remix or Next.js for recent React projects.

Ben HoustonFebruary 18, 20254 min read

I have used Next.js and Remix on real projects, and I kept losing time to the same class of mistakes: routing behavior hidden in conventions, server code crossing the client boundary in surprising ways, and errors that showed up after the app ran. TanStack/Start is what I use now, on this website and in client work. Its route and server-function APIs catch more of those mistakes while I am still editing.

Switching Frameworks#

I started with Next.js. The SSR support worked well at first, but I hit the same rough edges as projects grew: implicit routing that took too long to trace, server actions with forced serialization that broke type safety, and runtime errors the compiler never flagged. I moved to Remix next, hoping its convention-over-configuration approach would help. It did in some ways, but file-based conventions introduced their own traps. Export load instead of loader, or metadata instead of meta, and you can spend time debugging a typo instead of reading a type error.

TanStack/Start puts those contracts in code. You define routes, loaders, metadata, and server functions through APIs the compiler can check.

Project Structure and Routing#

TanStack/Start has a prescriptive project structure:

app/
├── routes/
│   ├── __root.tsx    # Root layout and configuration
│   └── index.tsx     # Individual routes
├── client.tsx        # Client-side entry
├── router.tsx        # Router configuration
└── ssr.tsx           # Server-side entry

If you are coming from Next.js or Remix, the structure can feel rigid. I have come to like that constraint:

  • Type safety: Route definitions are functions, so the compiler checks the route shape before the browser sees it.
  • Clear separation: Route configuration and component logic sit in different places. You can read either one without pulling apart the other.
  • Fewer naming traps: You cannot export load instead of loader and wonder why the route skipped your data load. The API tells you what it expects.

Example: Defining a Route with createFileRoute#

TanStack/Start uses createFileRoute for route definitions:

// app/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router';
import React from 'react';

const IndexPage: React.FC = () => {
  return <div>Welcome to the Index Page</div>;
};

export const route = createFileRoute('/')({
  component: IndexPage,
});

(createFileRoute supports loaders and metadata, like Remix.)

Server Functions#

Server-side logic in TanStack/Start runs through createServerFn. Instead of framework-specific directives or 'use server' annotations with implicit serialization, you choose the method, validate the input, and handle the request:

const userSchema = z.object({ ... });

const createNewUser = createServerFn({ method: 'POST' })
  .validator((data) => {
    return zodSchema.parse(data);
  })
  .handler(async ({ data }) => {
    // Handle the validated data
  });
  • Explicit validation: You validate incoming data before it reaches the handler.
  • Typed call sites: Loaders, hooks, and components call the same server function without each one rebuilding the request shape.

I find this easier to review. The data shape sits next to the handler, and the client call still reads like ordinary TypeScript.

Data Management and Caching#

TanStack/Start pairs with TanStack Query. I became a fan after years of writing cache invalidation code by hand and then debugging stale screens after route changes. With TanStack Query tied into the router, the cache keys and route state stay connected.

In practice, I write less fetch-and-state plumbing, and I spend less time chasing stale UI after navigation.

Performance: Balancing Build and Runtime#

Runtime performance has worked well for the projects where I have used it. Build times are slower than I'd like. Nitro adds noticeable overhead, though the team is working to remove that dependency in future versions.

Full document SSR and streaming worked without extra setup in my use cases.

Migration Considerations#

If you are coming from Remix, the shift is manageable. Most of the work is replacing action patterns with server functions.

From Next.js, routing and server-side logic both need rethinking. In exchange, more mistakes surface in the editor instead of in production.

Build performance is the main caveat I would call out. For routing, server functions, SSR, and data loading, TanStack/Start has removed enough friction from my React projects that I now reach for it first.