> Как в SSR составляется состояние для рендера? (JavaScript)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: elirox

Стек: JavaScript

> Пример ответа

Короткий ответ

В SSR состояние для рендера собирается на сервере до отправки HTML. Сервер выполняет те же хуки инициализации, что и клиент (например, getInitialProps в Next.js или load в Remix), собирает данные из API, БД или кэша, формирует глобальный store, рендерит React-дерево в строку и сериализует состояние в тег <script> для гидратации на клиенте.

Подробное объяснение

Процесс состоит из нескольких этапов:

  1. Определение запроса - сервер получает URL, cookies, headers, извлекает параметры роута.

  2. Сбор данных - для каждого компонента или роута выполняются асинхронные функции загрузки (data fetching). В современных фреймворках это loader функции (Remix) или getServerSideProps (Next.js Pages Router), либо async setup в Vue.

  3. Формирование store - все полученные данные собираются в единый объект состояния. Это может быть Redux store, Zustand, Pinia или просто объект с ключами по страницам/компонентам.

  4. Рендеринг в строку - React-дерево рендерится в HTML строку с помощью renderToString или renderToPipeableStream. При этом store передаётся через Context или Provider.

  5. Сериализация и инъекция - состояние сериализуется в JSON (с учётом безопасности - экранирование </script> и <!--) и вставляется в HTML как <script>window.__INITIAL_STATE__ = ...</script>.

  6. Гидратация на клиенте - браузер загружает JS, читает __INITIAL_STATE__, инициализирует клиентский store тем же состоянием, React гидратирует DOM без потери серверного контента.

Ключевые моменты:

  • Состояние должно быть детерминированным - сервер и клиент должны получить одинаковые данные.
  • Нельзя сериализовать функции, классы, циклические ссылки - только plain objects.
  • Для стриминга (React 18+) состояние может отправляться частями через Suspense.

На практике

В реальных проектах используется один из паттернов:

  • Next.js Pages Router: getServerSideProps возвращает props, которые передаются в компонент и сериализуются автоматически в __NEXT_DATA__.
  • Next.js App Router: серверные компоненты рендерятся на сервере, состояние не передаётся явно - клиентские компоненты получают только пропсы.
  • Remix: loader возвращает данные, которые доступны через useLoaderData - фреймворк сам управляет сериализацией.
  • Custom SSR: разработчик вручную собирает store, рендерит и инжектит скрипт.

Важные практические аспекты:

  • Размер состояния - слишком большой JSON замедляет TTI (Time to Interactive). Используйте только минимально необходимые данные.
  • Безопасность - не включайте токены, пароли или секреты в __INITIAL_STATE__. Данные уходят клиенту.
  • Кэширование - на сервере можно кэшировать результаты загрузки данных для одинаковых запросов.
  • Ошибки - если загрузка данных упала, сервер должен вернуть fallback или 500, не отправляя невалидное состояние.

Пример кода

JAVASCRIPT
// Серверный entry point (Express + React)
import { renderToString } from 'react-dom/server';
import { createStore, Provider } from 'my-state-library';
async function handleRequest(req, res) {
// 1. Сбор данных
const pageData = await fetchPageData(req.url);
const userData = await fetchUserData(req.cookies.sessionId);
// 2. Формирование store
const initialState = {
page: pageData,
user: userData,
};
const store = createStore(initialState);
// 3. Рендеринг
const appHtml = renderToString(
<Provider store={store}>
<App url={req.url} />
</Provider>
);
// 4. Сериализация (экранируем </script>)
const safeState = JSON.stringify(initialState)
.replace(/</g, '\\u003c')
.replace(/-->/g, '--\\u003e');
// 5. HTML с инъекцией
const html = `
<!DOCTYPE html>
<html>
<head>...</head>
<body>
<div id="root">${appHtml}</div>
<script>window.__INITIAL_STATE__ = ${safeState};</script>
<script src="/bundle.js"></script>
</body>
</html>
`;
res.send(html);
}

Как отвечать на собеседовании

Начни с краткого определения SSR и цели - отдать готовый HTML с данными. Затем опиши последовательность шагов, акцентируя внимание на моменте "сервер собирает данные → рендерит → сериализует". Упомяни конкретные фреймворки (Next.js, Remix) как примеры реализации. Подчеркни разницу между старым подходом (весь state в одном скрипте) и новым (стриминг, Server Components). Важно: скажи, что состояние должно быть чистым JSON без функций. Если спросят про гидратацию - объясни, что клиент использует то же состояние для первого рендера, чтобы не было mismatch.

Что проверяет интервьюер

  • Понимание жизненного цикла SSR: от запроса до гидратации.
  • Знание механизмов data fetching на сервере (loader, getServerSideProps).
  • Умение объяснить, как избежать race conditions и дублирования запросов.
  • Осведомлённость о безопасности сериализации (XSS через state).
  • Понимание trade-off между размером initial state и производительностью.
  • Знание современных подходов (React Server Components, streaming).

Типичные ошибки

  • Путаница между серверным и клиентским состоянием - думают, что state живёт только на клиенте.
  • Предложение сериализовать функции или промисы в __INITIAL_STATE__.
  • Игнорирование экранирования при вставке JSON в HTML (риск XSS).
  • Утверждение, что SSR всегда медленнее CSR из-за загрузки данных - не учитывают кэширование.
  • Непонимание, что при гидратации клиентский рендер должен совпадать с серверным (hydration mismatch).
  • Забывают про стриминг и думают, что весь HTML отдаётся одной порцией.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью