React Router v7: Full-Stack Routing with RSC

React Router v7 arrived in November 2024 as the consolidation of two histories: the ubiquitous React Router API that powered a decade of client-side navigation, and Remix’s full-stack conventions—file-based route modules, nested layouts, loaders, actions, and form-centric patterns that survive without JavaScript. For TypeScript developers, v7 is best understood as “routing plus server integration” first, and “type inference” second: the system generates types for route modules so loaders, actions, and components share a single contract, but you still import those types explicitly rather than leaning on a fully implicit graph the way TanStack Router does on the client.

The install base is massive—on the order of twelve million weekly downloads—which matters for hiring, plugins, examples, and long-tail Stack Overflow answers. Popularity is not quality, but it is risk reduction when you standardize a core dependency.

Generated types for route modules

React Router v7’s toolchain can emit types adjacent to your route files. The Route type becomes the hub for loader arguments, action arguments, and component props derived from what the module exports.

import type { Route } from './+types/posts.$postId'

export async function loader({ params }: Route.LoaderArgs) {
  const post = await fetchPost(params.postId)
  return { post }
}

export default function PostPage({ loaderData }: Route.ComponentProps) {
  const { post } = loaderData
  return <div>{post.title}</div>
}

export async function action({ request, params }: Route.ActionArgs) {
  const formData = await request.formData()
  const title = formData.get('title') as string
  await updatePost(params.postId, { title })
  return redirect(`/posts/${params.postId}`)
}

Route.LoaderArgs ties params to the dynamic segments of posts.$postId. Route.ComponentProps carries loaderData whose shape is inferred from the loader’s return type—no hand-maintained LoaderData interface that lags behind the fetch. Actions receive the same param typing, which keeps mutations aligned with reads.

The remaining rough edge is visible in the example: formData.get still returns FormDataEntryValue | null, so you will often pair route-level typing with Zod, Valibot, or small parsers at the action boundary. React Router v7 improves the routing contract; it does not eliminate all stringly-typed HTTP surfaces.

A disciplined pattern is to parse once inside the action, return typed errors as data (or field errors) for the UI, and keep the route module’s exported types as the single description of success payloads. That mirrors how mature Remix apps evolved before the merge: actions are not just “mutate and redirect,” they are validation gates with structured outcomes.

React Server Components and full-stack mode

Where v7 pulls ahead for 2026 architecture is full-stack routing with React Server Components in supported configurations. The same route module mindset—loaders as data gates, nested layouts as composition units—maps onto environments where part of the tree renders on the server and streams to the client. The exact adapter and bundler details evolve; the durable idea is that your route module is the spine of both URL and data dependencies.

Framework mode versus data mode (and the surrounding React Router 7 documentation) is worth reading whenever you adopt v7 on an existing app: where loaders run, how static assets are served, and how you opt into server features change the operational picture more than the JSX does. TypeScript helps inside each mode, but deployment determines which types even exist at runtime—a Request in a loader is not the same problem as window in a purely static build.

Nested layouts matter for TypeScript users because a parent route’s loader data flows to outlet children through the framework’s component props, and generated Route types keep that relationship traceable. When you add a child route, you are not guessing which props appear—your editor surfaces the shape, and breaking changes in the parent loader return type ripple to children the same way any other typed prop would.

If you are comparing against TanStack Router, be honest about the axis: TanStack optimizes client-side type inference ergonomics; React Router v7 optimizes integration with server rendering, actions, and the merged Remix feature set. Teams building SPAs with no SSR may carry more framework than they need if they default to v7 purely from habit.

Protection at the loader

Authorization belongs as close to data entry as possible. In v7, a loader is a natural choke point: before the route commits, you can validate session, roles, or entitlements and redirect early.

export async function loader({ request, params }: Route.LoaderArgs) {
  const session = await getSession(request);
  if (!session) {
    throw redirect("/login");
  }
  return { user: session.user };
}

This pattern composes with nested routes: parent loaders establish invariants; child loaders add specifics. TypeScript ensures params and the generated Route types stay consistent as you split modules. The security story is still “defense in depth”—never trust the client—but the typed loader surface makes it harder to accidentally read the wrong param name when copying patterns between routes.

TanStack Router vs React Router v7: a blunt comparison

Reach for React Router v7 when you are building a full-stack app with server rendering, RSC where applicable, nested layouts that track product structure, and actions that should work with progressive enhancement. It is the default dialect of a huge slice of the React job market, and the Remix merge means you are investing in a documented server-router story, not only client transitions.

Reach for TanStack Router when you are shipping a TypeScript-first SPA, want Link/navigate checked against the route tree with minimal boilerplate, and treat Zod-validated search params as a core requirement. TanStack’s inference is more seamless on the client; v7’s types are powerful but more explicit and more entangled with build tooling.

Use Next.js App Router (or another meta-framework) when the framework already owns routing, layouts, and server components—you are not choosing between these two packages in the same way; you are choosing conventions imposed by the platform.

Practical takeaway

React Router v7 is the mainstream full-stack answer with generated route types, strong loader/action ergonomics, and a roadmap tied to how React itself evolves around server components and streaming. It will not remove every as from your codebase, especially around forms and headers, but it does remove a whole class of “loader return shape drift” bugs. Pair it with disciplined parsing at boundaries, and you get a routing layer that matches how large teams actually ship in 2026—not only on the client, but across the wire.

If you lead a platform team, document one blessed pattern for loaders (auth, error mapping, JSON versus redirect) and one for actions (schema validation, idempotency keys where needed). Generated Route types multiply the value of those patterns because every route module becomes self-documenting. The goal is not perfection on day one; it is making the wrong thing harder to type than the right thing—the same bar TypeScript developers expect from the rest of the stack.