Context і SSR
Що робить React Context
React Context дозволяє:
- зберігати "глобальні" дані (наприклад,
user,theme,locale,auth), - передавати їх глибоко в дерево компонентів без пропів.
Приклад (клієнтський):
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Page />
</ThemeContext.Provider>
);
}Що відбувається при SSR
При Server-Side Rendering React:
- створює React-дерево на сервері,
- рендерить його з поточними контекстними значеннями в HTML,
- відправляє HTML клієнту,
- клієнт виконує hydration - "оживляє" той самий контекст на стороні браузера.
Тобто Context бере участь у рендерингу HTML, і саме його значення визначають, що сервер покладе в HTML (наприклад, темну тему або ім'я користувача).
Так, контекст працює при SSR
Приклад:
// theme-context.ts
import { createContext, useContext } from "react";
export const ThemeContext = createContext("light");
export const useTheme = () => useContext(ThemeContext);// App.tsx
import { ThemeContext } from "./theme-context";
export function App({ theme }: { theme: string }) {
return (
<ThemeContext.Provider value={theme}>
<Page />
</ThemeContext.Provider>
);
}// server.tsx
import { renderToString } from "react-dom/server";
import { App } from "./App";
app.get("/", (req, res) => {
const html = renderToString(<App theme="dark" />);
res.send(html);
});Сервер рендерить сторінку одразу в темній темі.
При гідратації клієнт отримує той самий контекст (value="dark"), і React не робить повторний рендер.
Але! Контекст має бути унікальним для кожного запиту
Контекст на сервері - глобальний об'єкт React, тому якщо ти використовуєш один і той самий екземпляр для всіх користувачів, він може "протекти" між запитами.
Приклад небезпечного коду:
// спільний контекст для всіх користувачів - небезпечно
const UserContext = createContext(null);
app.get("/", (req, res) => {
UserContext._currentValue = { id: 1, name: "Alice" };
const html = renderToString(<App />);
res.send(html);
});→ Якщо наступний користувач - "Bob", він може побачити дані "Alice". Це race condition між запитами.
Як правильно: "ізолювати контекст на запит"
Створюй Provider всередині рендеру кожного SSR-запиту:
const UserContext = createContext(null);
app.get("/", async (req, res) => {
const user = await getUserFromSession(req.cookies.token);
const html = renderToString(
<UserContext.Provider value={user}>
<App />
</UserContext.Provider>
);
res.send(html);
});Так у кожного запиту буде свій екземпляр контексту і свої дані.
Як це працює в Next.js
У Next.js це все автоматизовано:
// layout.tsx
export default function RootLayout({ children }) {
const theme = cookies().get('theme')?.value || 'light';
return (
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
);
}- На сервері
cookies()читаються при кожному запиті. - Контекст створюється унікально для рендеру сторінки.
- На клієнті він гідратується без розсинхронізації.
Що важливо пам'ятати
| Особливість | Пояснення |
|---|---|
| Працює при SSR | Context бере участь у серверному рендерингу |
| Передається в HTML | Контекстне значення впливає на HTML, який бачить користувач |
| Не можна зберігати глобально | Інакше дані "протечуть" між користувачами |
| Гідратація має збігатися | Контекст на клієнті повинен мати те саме значення, що й на сервері |
| Можна використовувати кілька контекстів | Наприклад, UserContext, ThemeContext, LocaleContext одночасно |
Висновок
React Context чудово працює з SSR, якщо:
- Ти створюєш
Providerвсередині кожного SSR-запиту (унікальний екземпляр). - Значення контексту збігаються на сервері і клієнті (інакше гідратація "зламається").
- Не виконуєш DOM-операції всередині контекстних ефектів на сервері.
Короткий рецепт безпечного SSR + Context
// server.tsx
renderToString(
<AuthProvider user={user}>
<ThemeProvider theme="dark">
<App />
</ThemeProvider>
</AuthProvider>
);- Кожному запиту - свій
Provider - Контексти передаються через props
- На клієнті і сервері - однакове дерево провайдерів
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.