Setting Status Codes with StaticRouter

Plain <StaticRouter> no longer takes a "context" attribute, so the old trick of mutating a context object during render to smuggle out a redirect URL or HTTP status code no longer works. The current way to get a real status code out of server-side rendering is to use a data router on the server too: createStaticHandler() runs your loaders/actions and computes the right status for you, and createStaticRouter() + <StaticRouterProvider> render the result.


// routes.js
import { redirect } from "react-router";

function About() { return <h1>About</h1>; }
function Dashboard() { return <h1>Dashboard</h1>; }
function NotFound() {
  return (
    <div>
      <h1>Sorry, can't find that.</h1>
    </div>
  );
}

export const routes = [
  { path: "/about", Component: About },
  { path: "/dashboard", Component: Dashboard },
  // A loader that only ever redirects - the status code is set right here
  { path: "/users", loader: () => redirect("/profiles", { status: 301 }) },
  { path: "/courses", loader: () => redirect("/dashboard", { status: 302 }) },
  { path: "*", Component: NotFound },
];

// server.js
import { createStaticHandler, createStaticRouter, StaticRouterProvider } from "react-router";
import { renderToString } from "react-dom/server";
import { routes } from "./routes.js";

const { query, dataRoutes } = createStaticHandler(routes);

export async function handleRequest(request) {
  // `request` must be a standard Fetch API Request - adapt your Node
  // req/res to one (e.g. with @react-router/node, or your framework's adapter).
  const context = await query(request);

  // A loader already redirected - its status code (301/302/etc) is baked
  // into this Response already, just return it as-is.
  if (context instanceof Response) {
    return context;
  }

  const router = createStaticRouter(dataRoutes, context);
  const html = renderToString(
    <StaticRouterProvider router={router} context={context} />
  );

  // context.statusCode is 404 automatically when no route matched (or
  // whatever a loader threw via `data(..., { status })`); 200 otherwise.
  return new Response(`<!doctype html><div id="app">${html}</div>`, {
    status: context.statusCode,
    headers: { "Content-Type": "text/html" },
  });
}