React Router является клиентским роутером, однако в приложениях с серверным рендерингом (SSR, Server-Side Rendering) требуется особый подход для корректной работы маршрутизации на сервере. Основная задача при SSR — синхронизация состояния маршрутов между сервером и клиентом, чтобы сервер мог отдать полностью подготовленную HTML-страницу, а клиент продолжал работу без перерендера.
Для SSR используется компонент ,
который отличается от тем, что не
зависит от объекта window и получает текущий путь через
проп location. Пример базового использования на
сервере:
import { StaticRouter } from "react-router-dom/server";
import { renderToString } from "react-dom/server";
import App from "./App";
function handleRequest(req, res) {
const context = {};
const html = renderToString(
);
if (context.url) {
res.redirect(context.url);
} else {
res.status(200).send(renderFullPage(html));
}
}
Ключевой момент: context позволяет передавать информацию
о редиректах, ошибках 404 и других событиях маршрутизации на сервере.
После рендеринга сервер проверяет context и при
необходимости выполняет перенаправление или возвращает статус
ошибки.
При SSR часто требуется возвращать корректный HTTP-статус в
зависимости от маршрута. Для этого React Router использует объект
context с полем statusCode:
}
loader={() => {
context.statusCode = 404;
}}
/>
На сервере после renderToString проверяется
context.statusCode и выставляется нужный статус:
res.status(context.statusCode || 200).send(renderFullPage(html));
Редиректы обрабатываются через context.url:
}
/>
Если используется внутри
, context.url будет
заполнено, и сервер сможет выполнить
res.redirect(context.url).
React Router поддерживает концепцию loaders, которые позволяют загружать данные для маршрута до рендеринга. В SSR это критично, чтобы сервер отдал полностью подготовленный HTML. Пример маршрута с loader:
import { createBrowserRouter, RouterProvider } from "react-router-dom";
const router = createBrowserRouter([
{
path: "/users/:id",
element: ,
loader: async ({ params }) => {
const response = await fetch(`https://api.example.com/users/${params.id}`);
return response.json();
}
}
]);
На сервере loader вызывается перед рендерингом:
async function handleRequest(req, res) {
const context = {};
const matches = matchRoutes(router.routes, req.url);
if (matches) {
for (const match of matches) {
if (match.route.loader) {
match.data = await match.route.loader({ params: match.params });
}
}
}
const html = renderToString(
);
res.status(200).send(renderFullPage(html));
}
Данные, полученные на сервере, затем передаются клиенту для гидратации без повторного запроса.
После SSR сервер отдает HTML вместе с JSON-данными маршрутов. Клиент
использует для продолжения
работы:
import { hydrateRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
const data = window.__ROUTE_DATA__;
hydrateRoot(
document.getElementById("root"),
);
Важно, чтобы routeData на клиенте совпадало с серверным
рендерингом, иначе произойдет перерендеринг и потеря состояния.
и
| Характеристика | BrowserRouter | StaticRouter |
|---|---|---|
| Доступ к window | Да | Нет |
| Обработка истории | Встроенная | Передается через location и context |
| Использование | Клиент | Сервер |
| Редиректы | Через Navigate | Через context.url |
| HTTP-статусы | Нет | Можно выставлять context.statusCode |
При SSR обязателен на сервере,
используется только на клиенте для
гидратации.
location и использовать
context для редиректов и ошибок.context.url
и context.statusCode после рендеринга на сервере, прежде
чем отправлять ответ.