StaticRouter для SSR

StaticRouter — это компонент из библиотеки React Router, предназначенный для использования в средах, где нет браузерного API для навигации, таких как сервер. В отличие от BrowserRouter, который работает на клиенте и управляет историей через API window.history, StaticRouter предоставляет возможность создавать маршрутизацию в статическом контексте, что необходимо для серверного рендеринга (Server-Side Rendering, SSR).


Основные свойства StaticRouter

StaticRouter имеет несколько ключевых пропсов, которые управляют его поведением:

  1. location Определяет текущий URL для маршрутизации. Обычно это строка, содержащая путь запроса пользователя. На сервере это может быть req.url из Express или любого другого HTTP-сервера.

    <StaticRouter location={req.url} context={context}>
        <App />
    </StaticRouter>
  2. context Объект, который позволяет компонентам внутри маршрутизатора взаимодействовать с серверной логикой. Например, при использовании <Redirect> информация о перенаправлении записывается в context, что позволяет серверу корректно ответить HTTP-статусом 301 или 302.

    const context = {};
    const html = renderToString(
        <StaticRouter location="/about" context={context}>
            <App />
        </StaticRouter>
    );
    
    if (context.url) {
        res.writeHead(302, { Location: context.url });
        res.end();
    }
  3. basename Задает базовый путь, который будет добавлен ко всем маршрутам. Полезен для приложений, размещенных в поддиректории сайта.


Отличия StaticRouter от BrowserRouter

Характеристика BrowserRouter StaticRouter
Место использования Клиент (браузер) Сервер
История Использует window.history Не использует историю
Навигация Динамическая Статическая, определяется пропсом location
Поддержка SSR Нет Да

Ключевой момент: StaticRouter не умеет управлять историей браузера. Его задача — предоставить контекст маршрутизации для корректного рендеринга компонентов React на сервере.


Пример использования в Express

import express from 'express';
import { StaticRouter } from 'react-router-dom/server';
import { renderToString } from 'react-dom/server';
import App from './App';

const app = express();

app.get('*', (req, res) => {
    const context = {};
    const html = renderToString(
        <StaticRouter location={req.url} context={context}>
            <App />
        </StaticRouter>
    );

    if (context.url) {
        res.redirect(301, context.url);
    } else {
        res.send(`
            <!DOCTYPE html>
            <html>
                <head>
                    <title>SSR App</title>
                </head>
                <body>
                    <div id="root">${html}</div>
                    <script src="/bundle.js"></script>
                </body>
            </html>
        `);
    }
});

app.listen(3000, () => console.log('Server running on port 3000'));

В этом примере:

  • location={req.url} передает путь запроса в маршрутизатор.
  • context используется для отслеживания перенаправлений или ошибок.
  • Результат renderToString возвращает готовый HTML для отправки клиенту.

Работа с <Redirect> и контекстом

При использовании SSR <Redirect> не может напрямую менять URL в браузере. Вместо этого он записывает информацию о редиректе в объект context. Сервер проверяет context.url и выполняет перенаправление с нужным HTTP-статусом.

import { Routes, Route, Navigate } from 'react-router-dom';

function App() {
    return (
        <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/login" element={<Login />} />
            <Route path="/dashboard" element={<RequireAuth><Dashboard /></RequireAuth>} />
            <Route path="*" element={<Navigate to="/" replace />} />
        </Routes>
    );
}

Если пользователь не аутентифицирован, компонент RequireAuth может использовать <Navigate> для перенаправления на /login. На сервере это будет отражено через context.url, что позволяет вернуть правильный HTTP-ответ.


Использование basename

basename позволяет учитывать базовую часть URL, если приложение размещено в поддиректории:

<StaticRouter location="/admin/users" context={context} basename="/admin">
    <App />
</StaticRouter>

В этом случае маршруты внутри <App> будут интерпретироваться относительно /admin.


Совместная работа с React Router v6

В React Router v6 для SSR:

  • Используется react-router-dom/server вместо обычного react-router-dom.
  • StaticRouter работает с renderToString или renderToPipeableStream (React 18).
  • Контекст (context) обеспечивает передачу информации о перенаправлениях и статусах страниц.
import { renderToPipeableStream } from 'react-dom/server';

const stream = renderToPipeableStream(
    <StaticRouter location={req.url} context={context}>
        <App />
    </StaticRouter>,
    {
        onShellReady() {
            res.statusCode = context.statusCode || 200;
            stream.pipe(res);
        }
    }
);

Важные моменты для SSR с StaticRouter

  • Контекст обязателен для обработки редиректов и ошибок.
  • location должен соответствовать пути запроса пользователя.
  • Не использовать компоненты, зависящие от window или history напрямую.
  • Для динамических редиректов и статусов страницы context обеспечивает безопасное взаимодействие между сервером и маршрутизатором.

StaticRouter является ключевым инструментом для построения универсальных React-приложений, где маршрутизация должна работать как на сервере, так и на клиенте, обеспечивая корректный SSR и управление HTTP-ответами.