В чем особенность React SSR?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
React Server-Side Rendering (SSR): особенности и подводные камни
React SSR — это рендеринг React компонентов на сервере (Node.js) с отправкой готового HTML клиенту. На первый взгляд это звучит просто, но SSR кардинально меняет архитектуру приложения и привносит множество сложностей.
Основная концепция SSR
// ТРАДИЦИОННЫЙ CSR (Client-Side Rendering)
// 1. Браузер получает пустой <div id="root"></div>
// 2. Загружает main.js (большой бандл)
// 3. JavaScript гидрирует DOM
// 4. Страница становится интерактивной
// Время до интерактивности: 3-5 сек
// SSR (Server-Side Rendering)
// 1. Сервер выполняет React код
// 2. Генерирует готовый HTML
// 3. Отправляет браузеру
// 4. Браузер отображает (быстро!)
// 5. JavaScript гидрирует страницу
// Время до интерактивности: 1-2 сек
Главные особенности React SSR
1. Гидратация (Hydration)
Гидратация — это когда JavaScript на клиенте "оживляет" уже отрисованный HTML:
// На сервере
import { renderToString } from 'react-dom/server';
const App = () => <h1>Hello {name}</h1>;
const html = renderToString(<App />);
// Результат: "<h1>Hello World</h1>"
// Отправляем браузеру
// На браузере
import { hydrateRoot } from 'react-dom/client';
const root = hydrateRoot(
document.getElementById('root'),
<App />
);
// React НЕ пересчитывает, а просто подключает обработчики событий
Важная особенность: React должен отрендерить ТУ ЖЕ структуру на клиенте, иначе будет ошибка "Hydration mismatch":
// ❌ ОШИБКА: Браузер выполняет другой код
const App = () => {
const [count, setCount] = useState(0);
return (
<div>
// На сервере: count = 0 (начальное значение)
// На клиенте: count = 5 (случайное)
Count: {count}
</div>
);
};
// ✅ ПРАВИЛЬНО: Используй useEffect для клиент-only кода
const App = () => {
const [count, setCount] = useState(0);
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
setCount(5); // Обновление после гидратации
}, []);
if (!isClient) return null; // На сервере пропускаем
return <div>Count: {count}</div>;
};
2. Отсутствие Browser API на сервере
На Node.js сервере нет window, document, localStorage, sessionStorage и других browser API:
// ❌ ОШИБКА: ReferenceError: window is not defined
const MyComponent = () => {
const theme = localStorage.getItem('theme'); // CRASH на сервере
return <div>{theme}</div>;
};
// ✅ ПРАВИЛЬНО: Проверяй наличие window
const MyComponent = () => {
const [theme, setTheme] = useState('light');
useEffect(() => {
if (typeof window !== 'undefined') {
const savedTheme = localStorage.getItem('theme');
setTheme(savedTheme || 'light');
}
}, []);
return <div>{theme}</div>;
};
// Или используй условное импортирование
const getTheme = () => {
if (typeof window !== 'undefined') {
return localStorage.getItem('theme');
}
return 'light'; // Дефолт на сервере
};
3. Streaming SSR (React 18+)
Вместо ожидания полной отрисовки, React может отправлять HTML по частям (streaming):
// Старый подход (SSR blocking)
import { renderToString } from 'react-dom/server';
const html = renderToString(<App />); // Ждём всё
response.send(html);
// Новый подход (Streaming)
import { renderToPipeableStream } from 'react-dom/server';
const { pipe } = renderToPipeableStream(<App />, {
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
},
onError(error) {
console.error(error);
}
});
Преимущество: Браузер получает HTML максимально быстро, может рендерить во время загрузки данных
4. Data Fetching на сервере
Од из главных преимуществ SSR — можно загружать данные на сервере ПЕРЕД отправкой HTML:
// Server Component (Next.js 13+)
export const getServerSideProps = async () => {
const posts = await fetch('https://api.example.com/posts');
return {
props: { posts }
};
};
export default function Blog({ posts }) {
// posts уже здесь, не нужен useEffect для загрузки
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
Когда браузер получает HTML, данные уже внутри. Это избегает waterfalls (жду загрузку JS -> жду загрузку данных).
5. Проблемы с SSR
a) Стек размножается:
// На сервере нужна одна версия React
// На клиенте нужна другая версия React
// Могут быть конфликты версий
b) Производительность сервера:
// Каждый запрос требует рендеринга React
// На тысячах пользователей это дорого
// Решение: кэширование, ISR (Incremental Static Regeneration)
c) Сложность разработки:
// Код должен работать и на сервере, и на клиенте
// Больше способов что-то сломать
// Больше edge cases
Сравнение подходов
CSR (Client-Side Rendering):
- Плюсы: Просто, быстрая разработка, масштабируемо на миллионы пользователей
- Минусы: Медленное время загрузки, нет SEO, большие JS бандлы
SSR (Server-Side Rendering):
- Плюсы: Быстрое время загрузки (FCP), хороший SEO, улучшенный UX
- Минусы: Сложная разработка, дорогой сервер, требует Node.js
SSG (Static Site Generation):
- Плюсы: Максимальная производительность, бесплатный хостинг (CDN), хороший SEO
- Минусы: Не подходит для динамичного контента
ISR (Incremental Static Regeneration) - Next.js:
- Плюсы: Комбинирует SSG и SSR — лучшее из обоих миров
- Минусы: Специфично для Next.js
Практический пример Next.js (SSR + ISR)
// next.config.js
module.exports = {
// SSR для динамического контента
getServerSideProps: async (context) => {
const post = await db.posts.findById(context.params.id);
return {
props: { post },
revalidate: 60 // ISR: переgenerate каждые 60 сек
};
}
};
// pages/posts/[id].js
export default function Post({ post }) {
return <h1>{post.title}</h1>;
}
Вывод
React SSR особенно полезен:
- SEO-зависимые сайты (блоги, новостные сайты)
- Медленные мобильные сети (быстрое FCP)
- Dynamic контент (курсы, профили пользователей)
- Улучшенный UX (контент видишь раньше, чем интерактивен)
Но добавляет сложность:
- Hydration mismatch ошибки
- Нет Browser API на сервере
- Больше производительности сервера
- Сложнее отлаживать
Мой совет: Используй SSR через Next.js с ISR — это даёт лучший баланс производительности и разработки. Чистый SSR часто избыточен для большинства проектов.