Skip to main content
Практика завдань

Шаблони серверних і клієнтських компонентів у Next.js

Патерни серверних і клієнтських компонентів

Знати, коли використовувати серверні проти клієнтських компонентів і як їх правильно складати — одна з найважливіших навичок Next.js.


Правило

ПотребаВикористовувати
Отримати дані, доступ до БДСерверний компонент
Статичний контент, SEOСерверний компонент
useState, useEffectКлієнтський компонент
onClick, onChangeКлієнтський компонент
API браузера (window, localStorage)Клієнтський компонент
Зменшити JS бандлСерверний компонент

Патерн 1: Перемістіть клієнтські компоненти вниз

Перемістіть інтерактивність до найменшого можливого клієнтського компонента:

tsx
// ✅ ДОБРЕ — лише інтерактивна частина є клієнтським компонентом // 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>; }
tsx
// ❌ ПОГАНО — вся сторінка є клієнтським компонентом через одну кнопку "use client"; export default function BlogPost() { const [post, setPost] = useState(null); useEffect(() => { fetchPost().then(setPost) }, []); // Все виконується на клієнті, великий JS бандл }

Патерн 2: Серверний компонент як діти

Передавайте серверні компоненти через клієнтські компоненти, використовуючи дітей:

tsx
// 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: Отримання на сервері, взаємодія на клієнті

tsx
// 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: Провайдери контексту

Обгортайте клієнтські провайдери на рівні макету:

tsx
// 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> ); }

Загальні помилки

tsx
// ❌ Імпорт клієнтського компонента і передача функції 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" там, де це потрібно. Перемістіть клієнтські компоненти до листя вашого дерева компонентів. Використовуйте патерн дітей для вкладення серверних компонентів всередині клієнтських компонентів. Отримуйте дані в серверних компонентах і передавайте їх як серіалізовані пропси до клієнтських компонентів для інтерактивності.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?
Практика завдань