Шаблони серверних і клієнтських компонентів у Next.js
Патерни серверних і клієнтських компонентів
Знати, коли використовувати серверні проти клієнтських компонентів і як їх правильно складати — одна з найважливіших навичок Next.js.
Правило
| Потреба | Використовувати |
|---|---|
| Отримати дані, доступ до БД | Серверний компонент |
| Статичний контент, SEO | Серверний компонент |
| useState, useEffect | Клієнтський компонент |
| onClick, onChange | Клієнтський компонент |
| API браузера (window, localStorage) | Клієнтський компонент |
| Зменшити JS бандл | Серверний компонент |
Патерн 1: Перемістіть клієнтські компоненти вниз
Перемістіть інтерактивність до найменшого можливого клієнтського компонента:
// ✅ ДОБРЕ — лише інтерактивна частина є клієнтським компонентом
// app/blog/[slug]/page.tsx (Серверний компонент)
export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug);
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p> {/* Статичний — залишається на сервері */}
<ShareButton url={post.url} /> {/* Лише це є клієнтським компонентом */}
</article>
);
}
// components/ShareButton.tsx
"use client";
export function ShareButton({ url }: { url: string }) {
return <button onClick={() => navigator.share({ url })}>Поділитися</button>;
}// ❌ ПОГАНО — вся сторінка є клієнтським компонентом через одну кнопку
"use client";
export default function BlogPost() {
const [post, setPost] = useState(null);
useEffect(() => { fetchPost().then(setPost) }, []);
// Все виконується на клієнті, великий JS бандл
}Патерн 2: Серверний компонент як діти
Передавайте серверні компоненти через клієнтські компоненти, використовуючи дітей:
// ClientWrapper.tsx
"use client";
import { useState } from "react";
export function Accordion({ title, children }: {
title: string;
children: React.ReactNode;
}) {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>{title}</button>
{isOpen && <div>{children}</div>}
</div>
);
}
// page.tsx (Серверний компонент)
export default async function Page() {
const data = await fetchData(); // Отримання даних на сервері
return (
<Accordion title="Деталі">
{/* Цей ServerContent виконується на сервері, навіть всередині клієнтського Accordion */}
<ServerContent data={data} />
</Accordion>
);
}Патерн 3: Отримання на сервері, взаємодія на клієнті
// app/products/page.tsx (Серверний компонент)
export default async function ProductsPage() {
const products = await getProducts(); // Сервер: API виклик не потрібен
return <ProductGrid products={products} />;
}
// components/ProductGrid.tsx
"use client";
export function ProductGrid({ products }: { products: Product[] }) {
const [filter, setFilter] = useState("");
const filtered = products.filter(p => p.name.includes(filter));
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
{filtered.map(p => <ProductCard key={p.id} product={p} />)}
</div>
);
}Патерн 4: Провайдери контексту
Обгортайте клієнтські провайдери на рівні макету:
// providers.tsx
"use client";
import { ThemeProvider } from "next-themes";
import { QueryClientProvider } from "@tanstack/react-query";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</ThemeProvider>
);
}
// app/layout.tsx (Серверний компонент)
import { Providers } from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<Providers>
{children} {/* Все ще можуть бути серверні компоненти */}
</Providers>
</body>
</html>
);
}Загальні помилки
// ❌ Імпорт клієнтського компонента і передача функції
import { ClientComp } from "./ClientComp";
export default function Server() {
// Не можна передавати функції з сервера на клієнт
return <ClientComp onClick={() => console.log("привіт")} />; // ❌ Помилка
}
// ✅ Визначте обробник у самому клієнтському компоненті
"use client";
export function ClientComp() {
return <button onClick={() => console.log("привіт")}>Натисніть</button>;
}Швидкий посібник для прийняття рішень
Чи потрібен стан, ефекти або обробники подій?
→ Клієнтський компонент ("use client")
Чи отримує дані або отримує доступ до серверних ресурсів?
→ Серверний компонент (за замовчуванням)
Чи є це чисто презентаційним (тільки пропси → JSX)?
→ Серверний компонент (за замовчуванням, зберігає бандл малим)
Чи використовує це API браузера (window, localStorage)?
→ Клієнтський компонент ("use client")Важливо:
За замовчуванням використовуйте серверні компоненти і лише додавайте "use client" там, де це потрібно. Перемістіть клієнтські компоненти до листя вашого дерева компонентів. Використовуйте патерн дітей для вкладення серверних компонентів всередині клієнтських компонентів. Отримуйте дані в серверних компонентах і передавайте їх як серіалізовані пропси до клієнтських компонентів для інтерактивності.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.