Гидратация приложения

Гидратация (hydration) — процесс привязки уже отрендеренного на сервере HTML к React-компонентам на клиенте. В контексте React Router гидратация играет критическую роль в обеспечении маршрутизации и сохранении состояния навигации при серверном рендеринге (SSR).


Настройка серверного рендеринга с React Router

Для начала необходимо создать маршруты и настроить серверный рендеринг. Пример маршрутов:

import { createRoutesFromElements, Route } from "react-router-dom";
import App from "./App";
import Home from "./pages/Home";
import About from "./pages/About";

export const routes = createRoutesFromElements(
  }>
    } />
    } />
  
);

На сервере используется renderToString или renderToPipeableStream (в зависимости от версии React) для генерации HTML:

import { renderToString } from "react-dom/server";
import { StaticRouter } from "react-router-dom/server";
import { routes } from "./routes";
import { matchRoutes } from "react-router-dom";

function handleRequest(req, res) {
  const matches = matchRoutes(routes, req.url);

  const html = renderToString(
    
      
    
  );

  res.send(`
    
    
      SSR React Router
      
        
${html}
`); }

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


Гидратация на клиенте

После получения отрендеренного на сервере HTML необходимо подключить React-компоненты и сделать их интерактивными. Для этого используется hydrateRoot:

import { hydrateRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { App } from "./App";

const container = document.getElementById("root");

hydrateRoot(
  container,
  
    
  
);

Особенности:

  • BrowserRouter обеспечивает полноценную клиентскую навигацию.
  • HTML от сервера должен точно соответствовать виртуальному DOM на клиенте, иначе React выдаст предупреждение о несовпадении (hydration mismatch).

Работа с данными и маршрутизацией

При SSR часто используется предзагрузка данных для маршрутов. React Router предоставляет функции loader для этого:

import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { Home } from "./pages/Home";

const router = createBrowserRouter([
  {
    path: "/",
    element: ,
    loader: async () => {
      const data = await fetch("/api/data").then(res => res.json());
      return data;
    }
  }
]);

hydrateRoot(
  document.getElementById("root"),
  
);

Преимущества использования loader при гидратации:

  • Сервер может предзагружать данные, что ускоряет отображение страницы.
  • Клиент получает готовый JSON с данными, предотвращая лишние запросы после гидратации.
  • Сохраняется синхронность состояния между сервером и клиентом.

Решение проблем с несовпадением HTML

Часто при гидратации возникают ошибки из-за разницы в рендеринге серверного и клиентского HTML. Основные причины:

  1. Различия в состояниях: например, случайные идентификаторы или даты.
  2. Асинхронные эффекты, вызываемые только на клиенте.
  3. Использование window или других браузерных API на сервере.

Методы устранения:

  • Использовать useEffect для клиентских побочных эффектов.
  • Генерировать идентичный HTML на сервере и клиенте.
  • При необходимости использовать suppressHydrationWarning для компонентов, которые могут различаться.

Поддержка вложенных маршрутов и lazy-загрузки

React Router позволяет комбинировать гидратацию с ленивой загрузкой компонентов через React.lazy:

import { lazy, Suspense } from "react";

const About = lazy(() => import("./pages/About"));

Loading...
}> }/>

Важно: при SSR необходимо обеспечить, чтобы серверный HTML содержал начальный вид страницы до ленивой загрузки. Для этого часто используют loadable-components или аналогичные библиотеки, которые интегрируются с React Router.


Навигация после гидратации

После успешной гидратации маршруты становятся интерактивными:

  • Используется Link для навигации без перезагрузки страницы.
  • useNavigate позволяет программно изменять URL.
  • Состояние маршрутов (location, params) синхронизировано между сервером и клиентом.

Пример навигации:

import { useNavigate } from "react-router-dom";

function Button() {
  const navigate = useNavigate();
  return ;
}

Суммарная схема гидратации с React Router

  1. Сервер:

    • Генерация HTML через StaticRouter.
    • Предзагрузка данных с loader или аналогом.
  2. Клиент:

    • Гидратация HTML через hydrateRoot.
    • Подключение BrowserRouter для интерактивной навигации.
    • Использование RouterProvider и loader для поддержания синхронизации данных.

Эта схема обеспечивает плавный переход от серверного рендеринга к полноценной клиентской маршрутизации без перезагрузки страницы.