# React Router v7
React Router is a multi-strategy routing library for React that bridges the gap from React 18 to React 19. It serves as both a lightweight client-side routing library and a full-featured React framework with server-side rendering, streaming, and multi-platform deployment capabilities. The library provides platform-agnostic routing primitives through `react-router`, browser-specific bindings through `react-router-dom`, and comprehensive build tooling through `@react-router/dev`.
The architecture supports progressive enhancement, starting from basic HTML forms that work without JavaScript, to sophisticated client-side interactions with optimistic UI updates, prefetching, and view transitions. React Router v7 introduces advanced features like Single-Fetch data loading strategy, React Server Components integration, and unified request/response handling across Node.js, Cloudflare Workers, and other platforms through runtime adapters.
## Creating a Basic Router
Setup a client-side router with data loading and nested routes.
```tsx
import { createBrowserRouter, RouterProvider, useLoaderData } from "react-router-dom";
// Define loaders for data fetching
async function rootLoader() {
const user = await fetch("/api/user").then(r => r.json());
return { user };
}
async function todosLoader({ request, params }) {
const url = new URL(request.url);
const filter = url.searchParams.get("filter");
const todos = await fetch(`/api/todos?filter=${filter}`).then(r => r.json());
return { todos };
}
// Define route configuration
const router = createBrowserRouter([
{
path: "/",
loader: rootLoader,
Component: Root,
errorElement: ,
children: [
{
index: true,
Component: Home,
},
{
path: "todos",
loader: todosLoader,
Component: TodosList,
},
],
},
]);
function Root() {
const { user } = useLoaderData();
return (
{/* Nested routes render here */}
);
}
function TodosList() {
const { todos } = useLoaderData();
return (
{todos.map(todo => (
{todo.title}
))}
);
}
export default function App() {
return Loading...} />;
}
```
## Form Submission with Actions
Handle form submissions with server actions and automatic revalidation.
```tsx
import { Form, useActionData, useNavigation, redirect } from "react-router-dom";
// Define action for handling form submissions
async function createTodoAction({ request, params }) {
const formData = await request.formData();
const title = formData.get("title");
const description = formData.get("description");
// Validate input
if (!title || title.length < 3) {
return { error: "Title must be at least 3 characters" };
}
// Create todo
const todo = await fetch("/api/todos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title, description }),
}).then(r => r.json());
// Redirect to the new todo
return redirect(`/todos/${todo.id}`);
}
const router = createBrowserRouter([
{
path: "/todos/new",
action: createTodoAction,
Component: NewTodoForm,
},
]);
function NewTodoForm() {
const actionData = useActionData();
const navigation = useNavigation();
const isSubmitting = navigation.state === "submitting";
return (
);
}
```
## Navigation with Links
Create navigation links with prefetching and active state styling.
```tsx
import { Link, NavLink } from "react-router-dom";
function Navigation() {
return (
);
}
```
## Using Fetchers for Non-Navigation Submissions
Submit data without navigation using fetchers for dynamic interfaces.
```tsx
import { useFetcher, useFetchers } from "react-router-dom";
function TodoItem({ todo }) {
const fetcher = useFetcher();
const isDeleting = fetcher.state === "submitting";
const isOptimistic = fetcher.formData?.get("completed") === "true";
return (
);
}
function TodoErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error) && error.status === 404) {
return (
Todo not found
Back to todos
);
}
throw error; // Rethrow to parent boundary
}
async function todoLoader({ params }) {
const todo = await fetch(`/api/todos/${params.id}`).then(r => {
if (!r.ok) {
throw new Response("Not Found", { status: 404 });
}
return r.json();
});
return { todo };
}
```
## Deferred Data Loading
Stream data to the client as it becomes available using deferred loading.
```tsx
import { defer, Await, useLoaderData } from "react-router-dom";
import { Suspense } from "react";
async function loader({ params }) {
// Fast data - await immediately
const product = await fetchProduct(params.id);
// Slow data - defer loading
const reviews = fetchReviews(params.id); // Don't await
const recommendations = fetchRecommendations(params.id); // Don't await
return defer({
product,
reviews,
recommendations,
});
}
function ProductPage() {
const { product, reviews, recommendations } = useLoaderData();
return (
{/* Render immediately */}
{product.name}
{product.description}
{/* Stream in when ready */}
Loading reviews...
}>
Error loading reviews}
>
{(reviewsData) => (
Reviews
{reviewsData.map(review => (
{review.text}
))}
)}
Loading recommendations...}>
{(recs) => (
You might also like
{recs.map(rec => (
{rec.name}
))}
)}
);
}
```
## Server Request Handler
Create a server request handler for Node.js or other platforms.
```tsx
import { createRequestHandler } from "@react-router/node";
import * as build from "./build/server/index.js";
// Create request handler
const handler = createRequestHandler({
build,
mode: process.env.NODE_ENV,
getLoadContext: (req, res) => ({
db: initDatabase(),
user: req.user,
}),
});
// Use with Express
import express from "express";
const app = express();
app.all("*", handler);
app.listen(3000, () => {
console.log("Server running on http://localhost:3000");
});
// Use with native Node.js HTTP server
import { createServer } from "http";
import { createRequestListener } from "@react-router/node";
const server = createServer(
createRequestListener({
build,
mode: process.env.NODE_ENV,
})
);
server.listen(3000);
```
## Type-Safe Route Data
Leverage TypeScript for type-safe loader and action data.
```tsx
import type { LoaderFunctionArgs, ActionFunctionArgs } from "react-router-dom";
import { useLoaderData, useActionData } from "react-router-dom";
interface Todo {
id: string;
title: string;
completed: boolean;
}
async function loader({ params }: LoaderFunctionArgs) {
const todos = await db.todo.findMany();
return { todos };
}
async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const title = formData.get("title") as string;
if (!title) {
return { error: "Title is required" };
}
const todo = await db.todo.create({ data: { title } });
return { todo };
}
function TodosPage() {
// Type inferred from loader return type
const { todos } = useLoaderData();
const actionData = useActionData();
return (
{actionData?.error &&
{actionData.error}
}
{actionData?.todo &&
Created: {actionData.todo.title}
}
{todos.map(todo => (
{todo.title}
))}
);
}
```
## Blocking Navigation
Prevent navigation when there are unsaved changes.
```tsx
import { useBlocker } from "react-router-dom";
import { useState } from "react";
function EditForm() {
const [isDirty, setIsDirty] = useState(false);
const blocker = useBlocker(
({ currentLocation, nextLocation }) =>
isDirty && currentLocation.pathname !== nextLocation.pathname
);
return (
<>
{blocker.state === "blocked" && (
You have unsaved changes. Are you sure you want to leave?
)}
>
);
}
```
## View Transitions
Enable smooth visual transitions between routes using the View Transitions API.
```tsx
import { Link, useViewTransitionState } from "react-router-dom";
function Navigation() {
return (
);
}
function Thumbnail({ id, image }) {
// Check if this route is transitioning
const isTransitioning = useViewTransitionState(`/photos/${id}`);
return (
);
}
// In CSS
/*
::view-transition-old(photo-expand),
::view-transition-new(photo-expand) {
animation-duration: 0.5s;
}
*/
```
## React Router serves as both a minimalist routing library for existing React applications and a comprehensive framework for building full-stack web applications. In library mode, it provides declarative routing, data loading, and form handling that integrates seamlessly with any React setup. In framework mode with `@react-router/dev`, it delivers a complete solution with file-based routing, automatic code splitting, server-side rendering with streaming, and unified deployment across Node.js, Cloudflare Workers, and other platforms.
The integration patterns range from simple client-side routing with `createBrowserRouter()` to advanced server implementations with loaders, actions, and session management. The library's progressive enhancement philosophy ensures applications remain functional without JavaScript while enabling sophisticated interactions when available. Data loading strategies support both traditional waterfall loading and modern patterns like Single-Fetch for optimized performance. Session management abstractions work across cookie-based, file-based, and key-value storage backends, making it straightforward to build authentication and stateful features that work consistently across deployment targets.