> Как в SSR составляется состояние для рендера? (JavaScript)
Уровень: senior · Роль: frontend · Категория: Технические вопросы
Компании: elirox
Стек: JavaScript
> Пример ответа
Короткий ответ
В SSR состояние для рендера собирается на сервере до отправки HTML. Сервер выполняет те же хуки инициализации, что и клиент (например, getInitialProps в Next.js или load в Remix), собирает данные из API, БД или кэша, формирует глобальный store, рендерит React-дерево в строку и сериализует состояние в тег <script> для гидратации на клиенте.
Подробное объяснение
Процесс состоит из нескольких этапов:
-
Определение запроса - сервер получает URL, cookies, headers, извлекает параметры роута.
-
Сбор данных - для каждого компонента или роута выполняются асинхронные функции загрузки (data fetching). В современных фреймворках это
loaderфункции (Remix) илиgetServerSideProps(Next.js Pages Router), либоasync setupв Vue. -
Формирование store - все полученные данные собираются в единый объект состояния. Это может быть Redux store, Zustand, Pinia или просто объект с ключами по страницам/компонентам.
-
Рендеринг в строку - React-дерево рендерится в HTML строку с помощью
renderToStringилиrenderToPipeableStream. При этом store передаётся через Context или Provider. -
Сериализация и инъекция - состояние сериализуется в JSON (с учётом безопасности - экранирование
</script>и<!--) и вставляется в HTML как<script>window.__INITIAL_STATE__ = ...</script>. -
Гидратация на клиенте - браузер загружает 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. Формирование storeconst 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 отдаётся одной порцией.
> Похожие задачи по frontend
Как создаются переменные в JavaScript?
Как отправить состояние при закрытии страницы браузера?
В каком порядке срабатывают события при нажатии на кнопку и почему
Почему метод shift мутирует массив и как сделать удаление первого элемента иммутабельно в JavaScript?
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью