Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
React Router
https://github.com/remix-run/react-router
Admin
React Router is a multi-strategy router for React, bridging React 18 to 19, usable as a framework or
...
Tokens:
256,301
Snippets:
1,062
Trust Score:
7.5
Update:
7 hours ago
Context
Skills
Chat
Benchmark
89.4
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# React Router v7 React Router is a multi-strategy client-side (and server-side) routing library for React applications. Version 7 unifies the previously separate Remix and React Router projects, offering three distinct usage modes that add progressively more features: **Declarative** (basic URL-to-component matching), **Data** (adds loaders, actions, and fetchers via `createBrowserRouter`), and **Framework** (wraps Data mode with a Vite plugin for SSR, code-splitting, type-safe route modules, pre-rendering, and more). Every mode is available through the single `react-router` package. The library's core functionality centers on URL matching, nested route rendering, and data orchestration. In Framework mode, route modules export typed `loader`/`action` functions that run on the server or client, a default React component, plus optional `middleware`, `ErrorBoundary`, `HydrateFallback`, `links`, `meta`, and `headers` exports. Data mode mirrors this API through configuration objects passed to `createBrowserRouter`. Declarative mode retains the classic `<BrowserRouter>/<Routes>/<Route>` JSX API familiar from React Router v5/v6. All three modes share the same hook surface (`useNavigate`, `useParams`, `useLoaderData`, `useFetcher`, etc.) and component primitives (`<Link>`, `<NavLink>`, `<Form>`, `<Outlet>`). --- ## Installation (Framework Mode) Bootstrap a new project using the official scaffolding tool. ```shellscript npx create-react-router@latest my-app cd my-app npm i npm run dev # → http://localhost:5173 ``` --- ## `BrowserRouter` — Declarative mode root component Wraps your app with a router that uses the browser History API for client-side navigation. The simplest possible way to use React Router. ```tsx import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter, Routes, Route } from "react-router"; import Home from "./Home"; import About from "./About"; import Dashboard from "./Dashboard"; import Settings from "./Settings"; ReactDOM.createRoot(document.getElementById("root")!).render( <BrowserRouter> <Routes> <Route index element={<Home />} /> <Route path="about" element={<About />} /> <Route path="dashboard" element={<Dashboard />}> <Route index element={<DashboardHome />} /> <Route path="settings" element={<Settings />} /> </Route> <Route path="*" element={<NotFound />} /> </Routes> </BrowserRouter> ); ``` --- ## `createBrowserRouter` — Data mode router factory Creates a data router that manages navigation via `history.pushState`. Accepts a route array and optional configuration for hydration data, `dataStrategy`, `patchRoutesOnNavigation` (lazy "fog of war" route discovery), middleware context seeding, and observability instrumentations. ```tsx import { createBrowserRouter, RouterProvider } from "react-router"; import { createRoot } from "react-dom/client"; // Full-featured data router with nested routes, loaders, actions const router = createBrowserRouter([ { path: "/", Component: RootLayout, loader: async () => { const user = await getCurrentUser(); return { user }; }, children: [ { index: true, Component: Home }, { path: "products/:pid", Component: Product, loader: async ({ params }) => { const product = await fetchProduct(params.pid); if (!product) throw new Response("Not Found", { status: 404 }); return { product }; }, action: async ({ request }) => { const data = await request.formData(); await updateProduct(data); return redirect("/products"); }, }, ], }, ], { // Seed context for middleware/loaders on every navigation getContext() { const ctx = new RouterContextProvider(); ctx.set(sessionContext, getSession()); return ctx; }, // Lazy route discovery (fog of war) async patchRoutesOnNavigation({ patch, path }) { if (path.startsWith("/admin")) { const adminRoutes = await import("./routes/admin"); patch(null, adminRoutes.default); } }, }); createRoot(document.getElementById("root")!).render( <RouterProvider router={router} /> ); ``` --- ## `<RouterProvider>` — Data mode renderer Renders the UI for a `DataRouter`. Should sit at the top of the element tree. Import from `react-router/dom` in browser environments to get automatic `flushSync` wiring. ```tsx import { createBrowserRouter } from "react-router"; import { RouterProvider } from "react-router/dom"; import { createRoot } from "react-dom/client"; const router = createBrowserRouter(routes); createRoot(document.getElementById("root")!).render( <RouterProvider router={router} onError={(error, info) => { // Called once per error — ideal for error reporting console.error(error, info.location); reportToSentry(error, info); }} /> ); ``` --- ## `routes.ts` — Framework mode route configuration Required file in Framework mode that maps URL patterns to route module files. Use `route`, `index`, `layout`, and `prefix` helpers, or opt into filesystem conventions with `@react-router/fs-routes`. ```ts // app/routes.ts import { type RouteConfig, route, index, layout, prefix, } from "@react-router/dev/routes"; import { flatRoutes } from "@react-router/fs-routes"; export default [ index("./home.tsx"), route("about", "./about.tsx"), layout("./auth/layout.tsx", [ route("login", "./auth/login.tsx"), route("register", "./auth/register.tsx"), ]), ...prefix("concerts", [ index("./concerts/home.tsx"), route(":city", "./concerts/city.tsx"), route("trending", "./concerts/trending.tsx"), ]), // Mix in filesystem-based routes from a subdirectory ...(await flatRoutes({ rootDirectory: "fs-routes" })), ] satisfies RouteConfig; ``` --- ## `react-router.config.ts` — Framework mode build configuration Optional config file for customising SSR, app directory, prerendering, server bundles, route discovery, and future flags. ```ts // react-router.config.ts import type { Config } from "@react-router/dev/config"; export default { // Server-side rendering (default true); set false for SPA mode ssr: true, // Pre-render specific URLs to static HTML at build time async prerender() { const products = await fetchAllProductSlugs(); return ["/", "/about", ...products.map((p) => `/products/${p}`)]; }, // Basename for apps hosted on a sub-path basename: "/my-app", // Split server bundle per route branch (e.g., separate admin bundle) serverBundles: ({ branch }) => branch.some((r) => r.id === "admin") ? "admin" : "main", // Lazy route discovery mode routeDiscovery: { mode: "lazy", manifestPath: "/__manifest" }, // Opt into upcoming features future: { v8_middleware: true, }, } satisfies Config; ``` --- ## Route Module — `loader` and `clientLoader` `loader` runs on the server (or at build time for pre-rendering). `clientLoader` runs in the browser for client-side navigations and can optionally run during hydration. Both provide data to the component via `loaderData`. ```tsx // app/routes/product.tsx // route("products/:pid", "./routes/product.tsx") import type { Route } from "./+types/product"; // Server loader — tree-shaken from client bundle automatically export async function loader({ params }: Route.LoaderArgs) { const product = await db.product.findUnique({ where: { id: params.pid } }); if (!product) throw new Response("Not Found", { status: 404 }); return { product }; } // Client loader — runs on subsequent navigations in the browser export async function clientLoader({ serverLoader, params, }: Route.ClientLoaderArgs) { const cached = cache.get(params.pid); if (cached) return { product: cached }; const serverData = await serverLoader(); // call server loader when needed cache.set(params.pid, serverData.product); return serverData; } // Force client loader to run during initial hydration clientLoader.hydrate = true as const; export function HydrateFallback() { return <p>Loading product…</p>; } export default function Product({ loaderData }: Route.ComponentProps) { const { product } = loaderData; return ( <div> <h1>{product.name}</h1> <p>{product.description}</p> </div> ); } ``` --- ## Route Module — `action` and `clientAction` `action` handles form `POST`/`PATCH`/`PUT`/`DELETE` submissions on the server. After the action completes, all loaders on the page automatically revalidate. `clientAction` runs in the browser only and takes priority when both are defined. ```tsx // app/routes/edit-product.tsx import type { Route } from "./+types/edit-product"; import { Form, redirect } from "react-router"; export async function action({ request, params }: Route.ActionArgs) { const formData = await request.formData(); const name = String(formData.get("name")); const price = Number(formData.get("price")); if (!name) { return { errors: { name: "Name is required" } }; } await db.product.update({ where: { id: params.pid }, data: { name, price }, }); return redirect(`/products/${params.pid}`); } export default function EditProduct({ actionData }: Route.ComponentProps) { return ( <Form method="post"> <label> Name <input name="name" /> {actionData?.errors?.name && ( <span role="alert">{actionData.errors.name}</span> )} </label> <label> Price <input name="price" type="number" step="0.01" /> </label> <button type="submit">Save</button> </Form> ); } ``` --- ## Route Module — `ErrorBoundary` Replaces the route component when a `loader`, `action`, or rendering error occurs. Receives the error through `useRouteError`. ```tsx import { isRouteErrorResponse, useRouteError } from "react-router"; export function ErrorBoundary() { const error = useRouteError(); if (isRouteErrorResponse(error)) { return ( <main> <h1>{error.status} {error.statusText}</h1> <p>{error.data}</p> </main> ); } if (error instanceof Error) { return ( <main> <h1>Application Error</h1> <p>{error.message}</p> {import.meta.env.DEV && <pre>{error.stack}</pre>} </main> ); } return <h1>Unknown Error</h1>; } ``` --- ## Route Module — `middleware` and `clientMiddleware` Route middleware runs before and after route handlers in a nested chain (parent → child on the way in, child → parent on the way out). Enable with `future.v8_middleware: true` in `react-router.config.ts`. ```tsx // app/routes/dashboard.tsx import { redirect, createContext, RouterContextProvider } from "react-router"; import type { Route } from "./+types/dashboard"; // app/context.ts export const userContext = createContext<User | null>(null); // --- Authentication middleware (server) --- async function authMiddleware({ request, context }: Route.MiddlewareFunctionArgs) { const session = await getSession(request); const userId = session.get("userId"); if (!userId) throw redirect("/login"); context.set(userContext, await getUserById(userId)); } // --- Logging middleware (client) --- async function timingMiddleware( { request }: Route.ClientMiddlewareFunctionArgs, next: () => Promise<void> ) { const start = performance.now(); await next(); console.log(`Navigation took ${Math.round(performance.now() - start)}ms`); } export const middleware: Route.MiddlewareFunction[] = [authMiddleware]; export const clientMiddleware: Route.ClientMiddlewareFunction[] = [timingMiddleware]; export async function loader({ context }: Route.LoaderArgs) { const user = context.get(userContext); // type-safe — guaranteed User return { profile: await getProfile(user) }; } export default function Dashboard({ loaderData }: Route.ComponentProps) { return <h1>Welcome, {loaderData.profile.fullName}!</h1>; } ``` --- ## Route Module — `links` and `meta` `links` injects `<link>` elements (stylesheets, icons, preloads) into the document `<head>` via `<Links />`. `meta` defines page metadata rendered by `<Meta />`. ```tsx // app/routes/product.tsx import type { Route } from "./+types/product"; export function links(): Route.LinkDescriptors { return [ { rel: "stylesheet", href: "/styles/product.css" }, { rel: "preload", href: "/images/hero.webp", as: "image" }, ]; } export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors { return [ { title: data?.product.name ?? "Product Not Found" }, { property: "og:title", content: data?.product.name }, { name: "description", content: data?.product.description }, ]; } // app/root.tsx — render collected links and meta import { Links, Meta, Outlet, Scripts } from "react-router"; export default function Root() { return ( <html lang="en"> <head> <Meta /> <Links /> </head> <body> <Outlet /> <Scripts /> </body> </html> ); } ``` --- ## `<Link>` — Client-side navigation anchor A progressively enhanced `<a>` element that triggers client-side navigation. Supports prefetching, scroll reset control, view transitions, and URL masking. ```tsx import { Link } from "react-router"; // Basic navigation <Link to="/dashboard">Dashboard</Link> // Object form — pathname + search + hash <Link to={{ pathname: "/search", search: "?q=react", hash: "#results" }}> Search </Link> // Prefetch on hover (Framework mode) <Link to="/heavy-page" prefetch="intent">Heavy Page</Link> // Replace current history entry instead of push <Link to="/checkout" replace>Checkout</Link> // Carry state to the next route <Link to="/profile" state={{ from: "dashboard" }}>My Profile</Link> // Prevent scroll reset when navigating within the same page (tabs, etc.) <Link to="?tab=reviews" preventScrollReset>Reviews</Link> // View Transition API (Framework/Data mode) <Link to={`/images/${img.id}`} viewTransition> <img src={img.thumbnail} alt={img.alt} /> </Link> ``` --- ## `<NavLink>` — Active-state navigation link Extends `<Link>` with automatic `active`/`pending` CSS classes and `aria-current="page"` for the currently matched route. ```tsx import { NavLink } from "react-router"; // Automatic active/pending classes applied via CSS // a.active { color: red; } a.pending { opacity: 0.6; } <NavLink to="/home">Home</NavLink> // Conditional className via render prop <NavLink to="/settings" className={({ isActive, isPending }) => isActive ? "nav-active" : isPending ? "nav-pending" : "nav-link" } > Settings </NavLink> // Conditional children render prop <NavLink to="/messages"> {({ isActive, isPending }) => ( <span> Messages {isPending && <Spinner />} {isActive && <ActiveIndicator />} </span> )} </NavLink> // Only match exact path (not prefix) <NavLink to="/blog" end>Blog</NavLink> ``` --- ## `<Form>` — Progressive enhancement HTML form Submits to route `action` functions via `fetch` instead of a full page reload, triggering loaders to revalidate automatically. Falls back to a native `<form>` before JS loads. ```tsx import { Form, useNavigation } from "react-router"; export default function NewProject() { const navigation = useNavigation(); const isSubmitting = navigation.formAction === "/projects/new"; return ( <Form method="post" action="/projects/new"> <label> Title <input name="title" required /> </label> <label> Description <textarea name="description" /> </label> {/* File upload with multipart encoding */} <input name="cover" type="file" accept="image/*" /> <button type="submit" disabled={isSubmitting}> {isSubmitting ? "Creating…" : "Create Project"} </button> </Form> ); } // Use navigate={false} + fetcherKey to submit without navigation <Form method="post" navigate={false} fetcherKey="like-post"> <input type="hidden" name="postId" value={post.id} /> <button name="intent" value="like">Like</button> </Form> ``` --- ## `<Outlet>` — Child route render slot Renders the matching child route component inside its parent. Pass `context` to provide values to child routes. ```tsx import { Outlet, useOutletContext } from "react-router"; // Parent route export default function DashboardLayout() { const user = useCurrentUser(); return ( <div className="dashboard"> <Sidebar /> <main> {/* Pass data to all child routes */} <Outlet context={{ user }} /> </main> </div> ); } // Any child route export default function Settings() { const { user } = useOutletContext<{ user: User }>(); return <h1>Settings for {user.name}</h1>; } ``` --- ## `<Await>` — Deferred / streaming data rendering Renders a promise value (returned un-awaited from a `loader`) inside a `<React.Suspense>` boundary, enabling streaming HTML responses. ```tsx import { Await, useLoaderData } from "react-router"; import { Suspense } from "react"; export async function loader() { const book = await fetchBook(); // blocks — critical data const reviews = fetchReviews(); // not awaited — streamed const recommendations = fetchRecs(); // not awaited — streamed return { book, reviews, recommendations }; } export default function BookPage() { const { book, reviews, recommendations } = useLoaderData<typeof loader>(); return ( <div> <h1>{book.title}</h1> <Suspense fallback={<ReviewsSkeleton />}> <Await resolve={reviews} errorElement={<p>Could not load reviews.</p>} > {(resolvedReviews) => <ReviewList items={resolvedReviews} />} </Await> </Suspense> <Suspense fallback={<RecsSkeleton />}> <Await resolve={recommendations}> <RecommendationList /> {/* uses useAsyncValue() internally */} </Await> </Suspense> </div> ); } ``` --- ## `useLoaderData` — Access route loader data Returns the serialized data from the closest route's `loader` or `clientLoader`. In Framework mode, the type is automatically inferred from the loader's return type. ```tsx import { useLoaderData } from "react-router"; import type { Route } from "./+types/invoices"; export async function loader() { const invoices = await db.invoice.findMany({ where: { paid: false } }); return { invoices, count: invoices.length }; } export default function Invoices() { // Fully typed: { invoices: Invoice[], count: number } const { invoices, count } = useLoaderData<typeof loader>(); return ( <section> <h1>Outstanding Invoices ({count})</h1> <ul> {invoices.map((inv) => ( <li key={inv.id}>{inv.title} — ${inv.amount}</li> ))} </ul> </section> ); } ``` --- ## `useActionData` — Access most recent action result Returns the data returned from the most recent `POST` action submission, or `undefined` if no action has been called yet. Useful for form validation errors. ```tsx import { Form, useActionData } from "react-router"; export async function action({ request }) { const fd = await request.formData(); const email = String(fd.get("email")); const password = String(fd.get("password")); if (!email.includes("@")) return { error: "Invalid email address" }; if (password.length < 8) return { error: "Password too short" }; await createUser({ email, password }); return redirect("/dashboard"); } export default function Register() { const actionData = useActionData<typeof action>(); return ( <Form method="post"> <input name="email" type="email" placeholder="Email" /> <input name="password" type="password" placeholder="Password" /> {actionData?.error && ( <p role="alert" style={{ color: "red" }}> {actionData.error} </p> )} <button type="submit">Register</button> </Form> ); } ``` --- ## `useNavigate` — Programmatic navigation Returns a stable `navigate` function for imperative navigations in response to effects or user interactions. ```tsx import { useNavigate } from "react-router"; function CheckoutWizard({ step }: { step: number }) { const navigate = useNavigate(); function handleNext() { navigate({ pathname: "/checkout", search: `?step=${step + 1}` }); } function handleBack() { navigate(-1); // go back in history stack } async function handleSubmit() { const order = await submitOrder(); navigate(`/orders/${order.id}`, { replace: true }); // no "back" to checkout } return ( <> <button onClick={handleBack}>Back</button> <button onClick={handleNext}>Next</button> <button onClick={handleSubmit}>Place Order</button> </> ); } ``` --- ## `useParams` — Dynamic route parameters Returns the key/value object of dynamic `:param` segments from the matched URL. Child routes inherit all params from parent routes. ```tsx import { useParams } from "react-router"; // Route definition: "blog/:year/:month/:slug" export default function BlogPost() { const { year, month, slug } = useParams<{ year: string; month: string; slug: string; }>(); return ( <article> <h1>{slug?.replace(/-/g, " ")}</h1> <time>{year}-{month}</time> </article> ); } ``` --- ## `useSearchParams` — URL search parameter state Returns the current `URLSearchParams` and a setter that causes navigation when called. Accepts string, object, array, `URLSearchParams`, or a functional updater. ```tsx import { useSearchParams } from "react-router"; export default function ProductList() { const [searchParams, setSearchParams] = useSearchParams({ sort: "newest" }); const sort = searchParams.get("sort") ?? "newest"; const page = Number(searchParams.get("page") ?? "1"); return ( <div> <select value={sort} onChange={(e) => setSearchParams((prev) => { prev.set("sort", e.target.value); prev.delete("page"); // reset page on sort change return prev; }) } > <option value="newest">Newest</option> <option value="price-asc">Price ↑</option> <option value="price-desc">Price ↓</option> </select> <ProductGrid sort={sort} page={page} /> <button onClick={() => setSearchParams({ sort, page: String(page + 1) })}> Next Page </button> </div> ); } ``` --- ## `useLocation` — Current location object Returns the current `Location` object containing `pathname`, `search`, `hash`, and `state`. Triggers re-render on every navigation. ```tsx import { useEffect } from "react"; import { useLocation } from "react-router"; function Analytics() { const location = useLocation(); useEffect(() => { // Track every page view gtag("event", "page_view", { page_location: location.pathname + location.search, }); }, [location]); return null; } // Access state set by a previous <Link state={…}> function LoginPage() { const location = useLocation(); const from = (location.state as { from?: string })?.from ?? "/"; return <p>You'll be redirected to {from} after login.</p>; } ``` --- ## `useNavigation` — Global navigation pending state Returns the current `Navigation` object with `state` (`"idle" | "loading" | "submitting"`), the pending location, and submitted form data. ```tsx import { useNavigation, Outlet } from "react-router"; export default function Root() { const navigation = useNavigation(); return ( <html> <body> {/* Global loading bar */} {navigation.state !== "idle" && ( <div style={{ position: "fixed", top: 0, left: 0, right: 0, height: 3, background: "blue", }} /> )} {/* Show which form is being submitted */} {navigation.state === "submitting" && ( <p>Submitting to {navigation.formAction}…</p> )} <Outlet /> </body> </html> ); } ``` --- ## `useFetcher` — Non-navigating data interactions Loads data or submits to actions without causing a navigation. Each fetcher tracks its own `state`, `data`, and exposes `<fetcher.Form>`, `fetcher.submit()`, and `fetcher.load()`. Useful for likes, inline edits, auto-save, and concurrent mutations. ```tsx import { useFetcher } from "react-router"; // Shared fetcher via key — visible in multiple components function LikeButton({ postId }: { postId: string }) { const fetcher = useFetcher({ key: `like-${postId}` }); const isLiking = fetcher.state !== "idle"; return ( <fetcher.Form method="post" action="/api/likes"> <input type="hidden" name="postId" value={postId} /> <button type="submit" disabled={isLiking}> {isLiking ? "Liking…" : "Like"} </button> </fetcher.Form> ); } // Programmatic submit (e.g., auto-save on change) function AutoSaveNote({ note }: { note: Note }) { const fetcher = useFetcher(); function handleChange(e: React.ChangeEvent<HTMLTextAreaElement>) { fetcher.submit( { id: note.id, body: e.target.value }, { method: "patch", action: "/api/notes", encType: "application/json" } ); } return ( <div> <textarea defaultValue={note.body} onChange={handleChange} /> <span>{fetcher.state === "idle" ? "Saved" : "Saving…"}</span> </div> ); } // Load data without navigation function UserHoverCard({ userId }: { userId: string }) { const fetcher = useFetcher<User>(); return ( <div onMouseEnter={() => fetcher.load(`/api/users/${userId}`)} > {fetcher.data ? <UserCard user={fetcher.data} /> : <Skeleton />} </div> ); } ``` --- ## `useSubmit` — Imperative form submission The programmatic counterpart of `<Form>`. Submits data to an action from code (effects, timers, etc.) rather than user interaction. ```tsx import { useSubmit } from "react-router"; import { useEffect } from "react"; function SessionTimeoutWarning() { const submit = useSubmit(); useEffect(() => { const timer = setTimeout(() => { submit( { reason: "session_timeout" }, { action: "/logout", method: "post" } ); }, 30 * 60 * 1000); // 30 minutes return () => clearTimeout(timer); }, [submit]); return null; } ``` --- ## `useRevalidator` — Manual data revalidation Triggers revalidation of all loaders on the current page outside of normal navigation events (e.g., on window focus, WebSocket messages, polling intervals). ```tsx import { useRevalidator } from "react-router"; import { useEffect } from "react"; function RealtimeDashboard() { const revalidator = useRevalidator(); // Poll for updates every 30 seconds useEffect(() => { const id = setInterval(() => { if (revalidator.state === "idle") { revalidator.revalidate(); } }, 30_000); return () => clearInterval(id); }, [revalidator]); // Revalidate on browser tab focus useEffect(() => { function onFocus() { if (revalidator.state === "idle") revalidator.revalidate(); } window.addEventListener("focus", onFocus); return () => window.removeEventListener("focus", onFocus); }, [revalidator]); return ( <div> {revalidator.state !== "idle" && <RefreshingIndicator />} <MetricsGrid /> </div> ); } ``` --- ## `useMatches` — All active route matches Returns an array of all currently active route matches including their `id`, `pathname`, `params`, `data` (loaderData), and `handle`. Useful for building breadcrumbs or aggregating data from parent routes. ```tsx import { useMatches } from "react-router"; // Each route can export a handle to attach breadcrumb metadata export const handle = { breadcrumb: "Products" }; // Breadcrumb component that reads handle from all active routes function Breadcrumbs() { const matches = useMatches(); const crumbs = matches .filter((m) => m.handle && (m.handle as any).breadcrumb) .map((m) => ({ label: (m.handle as any).breadcrumb, to: m.pathname, })); return ( <nav aria-label="breadcrumb"> {crumbs.map((crumb, i) => ( <span key={crumb.to}> {i > 0 && " / "} <a href={crumb.to}>{crumb.label}</a> </span> ))} </nav> ); } ``` --- ## `useRouteError` and `isRouteErrorResponse` — Error boundary helpers `useRouteError` returns the error thrown in a loader, action, or component. `isRouteErrorResponse` narrows the type to a thrown `Response` with `status`, `statusText`, and `data`. ```tsx import { useRouteError, isRouteErrorResponse } from "react-router"; export function ErrorBoundary() { const error = useRouteError(); // HTTP error thrown as: throw new Response("Not Found", { status: 404 }) if (isRouteErrorResponse(error)) { if (error.status === 404) { return <h1>404 — Page Not Found</h1>; } if (error.status === 401) { return <h1>401 — Unauthorized</h1>; } return ( <h1> {error.status} {error.statusText} </h1> ); } if (error instanceof Error) { return ( <div> <h1>Something went wrong</h1> <p>{error.message}</p> </div> ); } return <h1>Unknown error</h1>; } ``` --- ## `useBlocker` — Navigation confirmation guard Prevents SPA navigations while a condition is true (e.g., unsaved form data). Returns a `Blocker` object to present a custom confirmation dialog. ```tsx import { useBlocker } from "react-router"; import { useState, useCallback } from "react"; export default function DraftEditor() { const [isDirty, setIsDirty] = useState(false); const blocker = useBlocker( useCallback( ({ currentLocation, nextLocation }) => isDirty && currentLocation.pathname !== nextLocation.pathname, [isDirty] ) ); return ( <div> <textarea onChange={() => setIsDirty(true)} placeholder="Write something…" /> {blocker.state === "blocked" && ( <dialog open> <p>You have unsaved changes. Leave anyway?</p> <button onClick={() => { setIsDirty(false); blocker.proceed(); }}> Leave </button> <button onClick={() => blocker.reset()}>Stay</button> </dialog> )} </div> ); } ``` --- ## `redirect` and `replace` — Loader/action redirects Helper functions that return a `Response` with a `Location` header to redirect the user from within a loader or action. `redirect` pushes a new history entry; `replace` replaces the current one. ```tsx import { redirect, replace } from "react-router"; // In a loader — redirect unauthenticated users export async function loader({ request }: LoaderFunctionArgs) { const session = await getSession(request); if (!session.userId) { // Preserve the original URL so the login page can redirect back after login const url = new URL(request.url); throw redirect(`/login?next=${encodeURIComponent(url.pathname)}`); } return fetchDashboardData(session.userId); } // In an action — replace history after successful mutation export async function action({ request }: ActionFunctionArgs) { const formData = await request.formData(); const result = await processOnboarding(formData); if (!result.ok) return { error: result.message }; // Replaces /onboarding in history so back button doesn't return to the form throw replace("/dashboard"); } ``` --- ## `createMemoryRouter` — In-memory router for testing Creates a router backed by in-memory history. Ideal for unit/integration tests and Node.js environments where `window.history` is unavailable. ```tsx import { createMemoryRouter, RouterProvider } from "react-router"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { routes } from "../app/routes"; test("navigates to product detail page", async () => { const router = createMemoryRouter(routes, { initialEntries: ["/products"], initialIndex: 0, }); render(<RouterProvider router={router} />); // Click first product link await userEvent.click(await screen.findByText("Widget Pro")); await waitFor(() => { expect(screen.getByRole("heading")).toHaveTextContent("Widget Pro"); }); }); test("shows 404 error boundary for unknown product", async () => { const router = createMemoryRouter(routes, { initialEntries: ["/products/nonexistent-id"], }); render(<RouterProvider router={router} />); await screen.findByText("404 — Page Not Found"); }); ``` --- ## Summary React Router v7 covers the full spectrum of routing needs in React applications. In **Declarative** mode it provides lightweight URL-to-component mapping with `<BrowserRouter>`, `<Routes>`, `<Route>`, and hooks like `useNavigate`, `useParams`, and `useLocation` — suitable for apps that already manage their own data fetching. **Data** mode adds co-located `loader`/`action` functions, automatic post-mutation revalidation, optimistic UI via `useFetcher`, and the `useNavigation`/`useRevalidator` pending-state APIs — all wired up through `createBrowserRouter` + `<RouterProvider>`. **Framework** mode builds on Data mode with a Vite plugin providing type-safe route modules, automatic code splitting, full SSR/SPA/pre-rendering support, route-level middleware, and the `react-router.config.ts` build configuration — making it a full-stack meta-framework comparable to Next.js or SvelteKit. Integration patterns vary by use case. Client-only SPAs typically start in Framework mode with `ssr: false` or in Data mode with `createBrowserRouter`, using `loader` for data fetching and `<Form>`/`useFetcher` for mutations. Server-rendered applications use Framework mode with `ssr: true`, deploying through adapters for Node/Express, Cloudflare Workers, Deno, or static hosts. Testing uses `createMemoryRouter` to render routes in isolation without a browser. Libraries and component packages can embed sub-routing with `<MemoryRouter>` or via `useRoutes`. Incremental migration from React Router v6 is supported through the `future` flag system and dedicated upgrade guides.