React Server Components
React Server Components (RSC) — компоненты, которые рендерятся исключительно на сервере и не отправляют JavaScript клиенту, что сокращает bundle-size и позволяет выполнять серверные операции прямо в компоненте.
Зачем нужно
Традиционный SSR с hydration: сервер рендерит HTML, клиент загружает весь JS-код компонентов для «оживления» интерфейса. RSC идут дальше: серверные компоненты вообще не включаются в JS-bundle — браузер получает только их HTML-вывод. Это уменьшает размер бандла, устраняет водопад данных (компонент запрашивает БД напрямую) и упрощает разработку fullstack-фич.
Где используется
- Next.js App Router (Server Components по умолчанию с Next.js 13+)
- Компоненты, работающие с базой данных или файловой системой напрямую
- Страницы с тяжёлыми зависимостями (парсеры markdown, PDF и т.д.), которые не нужны клиенту
- Компоненты без интерактивности (не используют useState, useEffect, обработчики событий)
Server vs Client Components
Server Component (по умолчанию в App Router)
────────────────────────────────────────────
✓ Прямой доступ к БД, файловой системе, API
✓ Не отправляет JS клиенту
✓ Поддерживает async/await
✗ Нет useState, useEffect, useContext
✗ Нет обработчиков событий (onClick и т.д.)
✗ Нет браузерных API (window, document)
Client Component ('use client' директива)
─────────────────────────────────────────
✓ Полноценный React: хуки, события, анимации
✓ Доступ к браузерным API
✗ Нет прямого доступа к серверным ресурсам
✗ Увеличивает JS-bundle
Server Component: прямой доступ к данным
// app/products/page.tsx — Server Component по умолчанию
// Нет 'use client' → выполняется только на сервере
import { db } from '@/lib/db';
// async компонент — работает только в Server Components
async function ProductsPage() {
// Прямой запрос к БД без API endpoint
const products = await db.products.findMany({
where: { published: true },
orderBy: { createdAt: 'desc' },
take: 20,
});
return (
<main>
<h1>Каталог</h1>
<ul>
{products.map(product => (
// ProductCard — тоже Server Component
<ProductCard key={product.id} product={product} />
))}
</ul>
</main>
);
}
export default ProductsPage;
Client Component для интерактивности
// components/AddToCartButton.tsx
'use client'; // ← директива: этот файл — Client Component
import { useState } from 'react';
interface Props {
productId: string;
}
export function AddToCartButton({ productId }: Props) {
const [loading, setLoading] = useState(false);
const handleAdd = async () => {
setLoading(true);
await addToCart(productId);
setLoading(false);
};
return (
<button onClick={handleAdd} disabled={loading}>
{loading ? 'Добавляем...' : 'В корзину'}
</button>
);
}
// app/products/[id]/page.tsx — Server Component
import { AddToCartButton } from '@/components/AddToCartButton';
async function ProductPage({ params }) {
const product = await db.products.findUnique({ where: { id: params.id } });
return (
<article>
<h1>{product.name}</h1>
<p>{product.description}</p>
{/* Server Component включает Client Component */}
<AddToCartButton productId={product.id} />
</article>
);
}
Частые ошибки
- Импорт серверного кода в Client Component —
import { db } from '@/lib/db'в'use client'файле вызовет ошибку; серверные зависимости нельзя использовать на клиенте. - Попытка передать несериализуемые данные как props — между Server и Client Component передаются только сериализуемые значения (не функции, не классовые экземпляры).
- Лишние
'use client'директивы — отмечают файл как Client Component не нужно, если он не использует хуки или события; это только увеличивает bundle.
Связанные темы
- _MOC SPA
- SSR -- Server Side Rendering
- Next.js -- обзор
- CSR, SSR, SSG -- обзор подходов
- Suspense и Concurrent Features