React Router Cheatsheet

Framework Mode and SSR

Use this React Router reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

SSR and Framework Mode Exports

// app/root.tsx
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";

export function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        {children}
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

export default function Root() {
  return <Outlet />;
}
Route exportRuns
defaultThe component, client and server
loaderServer only (framework mode)
clientLoaderBrowser only
actionServer only
clientActionBrowser only
ErrorBoundaryOn an error in this subtree
HydrateFallbackWhile a clientLoader runs on first load
metaDocument <title> and <meta> tags
links<link> tags, including preloads
headersResponse headers for this route
handleMetadata for useMatches
shouldRevalidateRevalidation control
export function meta({ data }: Route.MetaArgs) {
  return [
    { title: data.product.name },
    { name: "description", content: data.product.blurb },
  ];
}

export function headers() {
  return { "Cache-Control": "public, max-age=300, s-maxage=3600" };
}

Client-Only Loaders

// Skip the server entirely for this route's data
export async function clientLoader({ params, serverLoader }) {
  const cached = sessionStorage.getItem(`user:${params.id}`);
  if (cached) return JSON.parse(cached);
  const data = await serverLoader();      // fall through to the server loader
  sessionStorage.setItem(`user:${params.id}`, JSON.stringify(data));
  return data;
}
clientLoader.hydrate = true as const;     // run it on the first load too

export function HydrateFallback() {
  return <p>Loading…</p>;
}

HydrateFallback is required when clientLoader.hydrate is true, because on the first render there is no server data to show.

SPA Mode

// react-router.config.ts
export default { ssr: false } satisfies Config;

With ssr: false you still get the route config, loaders (as clientLoader), code splitting, and typegen, but the build produces a static index.html. This is the migration path for an existing SPA that wants the data APIs without a server.

Prerendering

export default {
  async prerender() {
    const slugs = await getPublishedSlugs();
    return ["/", "/about", ...slugs.map((s) => `/posts/${s}`)];
  },
} satisfies Config;

Prerendered routes are written to disk at build time and served as static files, so they cost nothing to serve and are fully crawlable.

Deploy Targets

AdapterFor
@react-router/nodeA Node server (Express, Fastify, or the built-in one)
@react-router/cloudflareCloudflare Workers and Pages
@react-router/architectAWS Lambda via Architect
@react-router/serveThe zero-config production server
npm run build           # -> build/client and build/server
npx react-router-serve ./build/server/index.js

Server Context

// A value your adapter injects, available in every loader and action
export async function loader({ context }: Route.LoaderArgs) {
  const user = await context.session.get("user");
  return { user };
}