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.