Антон Ларичев

Введение
Серверные компоненты в Next.js 15 изменили подход к построению веб-приложений на React. С переходом на App Router все компоненты по умолчанию выполняются на сервере, что открывает новые возможности для оптимизации производительности и SEO. Но значит ли это, что клиентские компоненты больше не нужны?
В этой статье разберём, как работают серверные и клиентские компоненты в Next.js 15, когда SSR (Server-Side Rendering) даёт преимущество перед CSR (Client-Side Rendering) и наоборот. Рассмотрим практические примеры и типичные ошибки.
Как работают серверные компоненты в Next.js 15
React Server Components (RSC) выполняются исключительно на сервере. Их код не попадает в JavaScript-бандл браузера, что уменьшает объём загружаемых данных. Сервер отрисовывает HTML и стримит его клиенту через React 18 Streaming.
// app/products/page.tsx — серверный компонент (по умолчанию)
async function ProductsPage() {
// Прямой запрос к базе данных — безопасно, код не уходит в браузер
const products = await db.product.findMany({
where: { isActive: true },
orderBy: { createdAt: 'desc' },
});
return (
<main>
<h1>Каталог товаров</h1>
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</main>
);
}
export default ProductsPage;
Серверный компонент может напрямую обращаться к базе данных, файловой системе или внутренним API. Это устраняет необходимость в промежуточном API-слое для получения данных.
Когда использовать клиентские компоненты
Клиентские компоненты нужны для интерактивности: обработки событий, управления состоянием через хуки и работы с API браузера. Чтобы объявить компонент клиентским, добавьте директиву "use client" в начало файла.
"use client";
// components/SearchFilter.tsx — клиентский компонент
import { useState, useTransition } from 'react';
import { useRouter } from 'next/navigation';
export function SearchFilter() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const router = useRouter();
const handleSearch = (value: string) => {
setQuery(value);
startTransition(() => {
// Обновляем URL с параметрами поиска
router.push(`/products?search=${encodeURIComponent(value)}`);
});
};
return (
<input
type="text"
value={query}
onChange={(e) => handleSearch(e.target.value)}
placeholder="Поиск товаров..."
className={isPending ? 'opacity-50' : ''}
/>
);
}
Какие задачи требуют директиву use client
Используйте "use client", когда компоненту нужны:
- Хуки React —
useState,useEffect,useRef,useContext - Обработчики событий —
onClick,onChange,onSubmit - API браузера —
localStorage,window,navigator,IntersectionObserver - Сторонние библиотеки с клиентской логикой — карусели, модальные окна, дропдауны
SSR vs CSR: сравнение подходов рендеринга
Разница между SSR и CSR в Next.js 15 проявляется на нескольких уровнях.
Производительность и время загрузки
SSR через серверные компоненты выигрывает по метрике Time to First Byte (TTFB) и First Contentful Paint (FCP). Пользователь видит контент до загрузки JavaScript. CSR требует сначала загрузить и выполнить JS-бандл, и только потом начинается рендеринг.
// SSR — данные приходят вместе с HTML
// app/blog/[slug]/page.tsx
async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug);
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
"use client";
// CSR — данные загружаются после монтирования
import { useEffect, useState } from 'react';
function Dashboard() {
const [stats, setStats] = useState(null);
useEffect(() => {
// Запрос выполняется в браузере после гидратации
fetch('/api/stats')
.then((res) => res.json())
.then(setStats);
}, []);
if (!stats) return <p>Загрузка...</p>;
return <StatsGrid data={stats} />;
}
SEO и индексация
Серверный рендеринг критически важен для страниц, которые должны индексироваться поисковиками: блоги, каталоги, лендинги. Поисковые роботы получают готовый HTML без необходимости выполнять JavaScript.
CSR подходит для закрытых разделов — личных кабинетов, дашбордов, админок, где SEO не играет роли.
Как передать серверный компонент в клиентский
Клиентские компоненты не могут импортировать серверные напрямую. Но есть паттерн композиции через children:
// components/InteractiveWrapper.tsx
"use client";
import { useState } from 'react';
export function InteractiveWrapper({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>
{isOpen ? 'Скрыть' : 'Показать'}
</button>
{isOpen && children}
</div>
);
}
// app/page.tsx — серверный компонент
import { InteractiveWrapper } from '@/components/InteractiveWrapper';
async function ServerContent() {
const data = await fetchData(); // Выполняется на сервере
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
export default function Page() {
return (
<InteractiveWrapper>
<ServerContent />
</InteractiveWrapper>
);
}
Серверный компонент ServerContent передаётся как children в клиентский InteractiveWrapper. Рендеринг серверного компонента происходит на сервере, а клиентский добавляет интерактивность.
Разница между SSR и серверными компонентами
Важно не путать классический SSR и React Server Components. При SSR (через getServerSideProps в Pages Router) весь компонент рендерится на сервере, но затем полностью гидратируется на клиенте — весь JavaScript отправляется в браузер.
Серверные компоненты в App Router работают иначе: их код вообще не попадает в клиентский бандл. Гидратация происходит только для клиентских компонентов, что существенно уменьшает размер JavaScript.
// Pages Router (старый SSR) — код уходит в бандл
export async function getServerSideProps() {
const data = await fetchData();
return { props: { data } };
}
// App Router (RSC) — код остаётся на сервере
async function Page() {
const data = await fetchData();
return <div>{data.title}</div>;
}
Частые ошибки при работе с серверными компонентами
Избыточное использование "use client". Не делайте клиентским весь компонент, если интерактивность нужна только в его части. Выносите интерактивную логику в отдельный мелкий клиентский компонент.
Попытка использовать хуки в серверном компоненте. useState, useEffect и другие хуки работают только в клиентских компонентах. Если видите ошибку гидратации — проверьте, не забыли ли вы директиву "use client".
Импорт серверного компонента в клиентский. Вместо прямого импорта используйте паттерн с children или props, как показано выше.
Хранение секретов в серверных компонентах без проверки. Хотя код серверных компонентов не попадает в бандл, убедитесь, что вы не передаёте секреты через props в клиентские компоненты.
Заключение
Серверные компоненты в Next.js 15 — это не замена клиентских, а дополнение. Используйте SSR и серверные компоненты для контентных страниц, SEO-критичных разделов и работы с данными. Оставьте CSR и клиентские компоненты для интерактивных элементов интерфейса. Гибридный подход, где серверные и клиентские компоненты комбинируются через композицию — это оптимальная стратегия для современных Next.js-приложений.



Комментарии
0