React Router Cheatsheet
Code Splitting and Prefetching
Use this React Router reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Code Splitting
// Data mode: `lazy` on the route { path: "reports", lazy: async () => { const mod = await import("./routes/reports"); return { Component: mod.default, loader: mod.loader }; }, }
lazy splits the loader too, which React.lazy cannot do. That matters, because a React.lazy route still has to download the component before the fetch can start, reintroducing the waterfall.
// Declarative mode: React.lazy plus Suspense const Reports = React.lazy(() => import("./Reports")); <Route path="reports" element={ <Suspense fallback={<Skeleton />}> <Reports /> </Suspense> } />
Framework mode splits every route automatically, so there is nothing to configure.
Prefetching
<Link to="/reports" prefetch="intent">Reports</Link>
| Value | Fetches when |
|---|---|
none | Never (the default) |
intent | On hover or focus |
render | As soon as the link renders |
viewport | When the link scrolls into view |
intent is the right default for most navigation: by the time a user finishes moving the mouse and clicking, the data and the code have already arrived.
Measuring the Split
npx vite-bundle-visualizer # what ended up in each chunk
npm run build -- --sourcemapIn framework mode the build prints a per-route chunk table, which is the fastest way to spot a route that accidentally imports a heavy dependency at the module level.
| Symptom | Cause |
|---|---|
| One giant chunk | A shared module imports every route eagerly |
| A route chunk pulls in a chart library | Move the import inside the component, or lazy it |
| Loader code in the main bundle | Use route lazy, not React.lazy |
Lazy Route Modules
// routes/reports.jsx exports what the router needs export async function loader({ request }) { /* … */ } export default function Reports() { /* … */ } export function ErrorBoundary() { /* … */ }
{
path: "reports",
lazy: () => import("./routes/reports").then((m) => ({
loader: m.loader,
Component: m.default,
ErrorBoundary: m.ErrorBoundary,
})),
}Keep path, index, and children static on the route object. Only the behavior (component, loader, action, boundary) can be lazy, because the router needs the shape of the tree to match a URL before it decides what to download.
Preloading Assets
// Framework mode: a links export becomes real <link> tags export function links() { return [ { rel: "preload", href: "/fonts/inter.woff2", as: "font", type: "font/woff2", crossOrigin: "anonymous" }, { rel: "preload", href: heroImage, as: "image" }, { rel: "stylesheet", href: styles }, ]; }