React Router Cheatsheet
Basics
Use this React Router reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
The Package
React Router v8 is one package, react-router. The react-router-dom
package was removed in v8. DOM specific entry points now live under
react-router/dom, and everything else comes from react-router.
| Package | What it gives you |
|---|---|
react-router | Router, hooks, components, server helpers |
react-router/dom | RouterProvider, HydratedRouter |
@react-router/dev | Vite plugin, CLI, route config helpers |
@react-router/node | Node adapter for the server build |
@react-router/serve | Ready made production server |
@react-router/fs-routes | Optional file based route conventions |
react-router-dom | Gone in v8, never install it for new work |
Baseline for v8: Node 22.22.0+, React 19.2.7+, Vite 7+, and the packages ship as ESM only.
Create an App
Framework mode is the default assumed setup on this sheet. It is React Router as a full stack framework on top of Vite, with server rendering, data loading, and typegen wired up for you.
npx create-react-router@latest my-app cd my-app npm run dev # vite dev server, HMR, typegen in watch mode npm run build # client + server builds into build/ npm start # react-router-serve build/server/index.js npx react-router routes # print the route tree npx react-router typegen # regenerate ./+types/* modules
Project Layout
my-app/
app/
root.tsx # document shell, Layout, root ErrorBoundary
routes.ts # the route config, all routes declared here
routes/
home.tsx # a route module
about.tsx
app.css
public/
react-router.config.ts # ssr, prerender, basename, future
vite.config.ts # reactRouter() plugin
.react-router/types/ # generated, gitignored// vite.config.ts import { reactRouter } from "@react-router/dev/vite"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [reactRouter()], });
A Route Module
One file per route. Exports are the API: the default export renders, and
loader / action run on the server. The generated Route namespace
types the props and the handler args from the route path.
// app/routes/product.tsx import type { Route } from "./+types/product"; import { Form } from "react-router"; export async function loader({ params }: Route.LoaderArgs) { return { product: await getProduct(params.id) }; } export async function action({ request }: Route.ActionArgs) { const form = await request.formData(); await addToCart(form.get("id")); return { added: true }; } export default function Product({ loaderData, actionData, }: Route.ComponentProps) { return ( <div> <h1>{loaderData.product.name}</h1> <Form method="post"> <button name="id" value={loaderData.product.id}>Add</button> </Form> {actionData?.added ? <p>In cart</p> : null} </div> ); }
// app/routes.ts import { type RouteConfig, index, route } from "@react-router/dev/routes"; export default [ index("routes/home.tsx"), route("products/:id", "routes/product.tsx"), ] satisfies RouteConfig;
The Three Modes
| Mode | You write | You get | Pick it when |
|---|---|---|---|
| Framework | routes.ts plus route modules, Vite plugin | SSR or SPA or prerender, loaders and actions, code splitting, typegen | Default. Any new app, and anything that wants server data |
| Data | createBrowserRouter route objects plus RouterProvider | Loaders, actions, fetchers, error boundaries, client side only | You already own the server and just want data APIs in the browser |
| Declarative | <BrowserRouter> plus <Routes> and <Route> | URL matching, Link, useNavigate, nothing else | A small client app, or you fetch with something else entirely |
Data and Declarative mode are the labeled exception throughout this sheet. They are still supported and still shipped from the same package, they just are not the starting point anymore.
Reading the Docs
Every API page on reactrouter.com is tagged with the modes it works in.
[MODES: framework] means the API only exists in Framework mode, and
[MODES: framework, data] covers both. Anything tagged unstable_ can
break in a minor release, so treat it as opt in.