Интеграция с фреймворками SSR

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 и при необходимости выполняет перенаправление или возвращает статус ошибки.


Поддержка редиректов и статусов HTTP

При 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 обязателен на сервере, используется только на клиенте для гидратации.


Важные рекомендации

  1. Синхронизация URL и состояния: всегда передавать текущий путь через location и использовать context для редиректов и ошибок.
  2. Асинхронная загрузка данных: использовать loader-методы маршрутов для предварительной подготовки данных.
  3. Гидратация: клиент должен получать те же данные, что и сервер, для предотвращения лишнего рендеринга.
  4. Редиректы и 404: проверять context.url и context.statusCode после рендеринга на сервере, прежде чем отправлять ответ.
  5. Модульность: хранить маршруты в отдельном модуле, чтобы их можно было использовать как на сервере, так и на клиенте.