Пошук уроків, статей та іншого контенту
Навчіться визначати, коли інтерактивність справді потребує Client Component, а коли достатньо серверного рендерингу.
У Next.js з App Router компоненти за замовчуванням є Server Components. Вони виконуються на сервері, можуть отримувати дані та формувати HTML до того, як сторінка потрапить у браузер.
Client Component потрібен тоді, коли компонент має виконувати код у браузері після завантаження сторінки:
реагувати на дії користувача через обробники подій;
зберігати локальний стан;
використовувати useState, useEffect та інші клієнтські хуки;
звертатися до window, document, localStorage, navigator;
використовувати API браузера;
працювати з бібліотекою, яка сама вимагає клієнтського середовища.
Для Client Component на початку файлу додають директиву:
'use client';Ця директива створює межу між серверною та клієнтською частинами застосунку.
Server Component підходить, якщо компонент:
лише відображає отримані дані;
не має локального стану;
не містить обробників подій;
не використовує браузерні API;
може бути повністю сформований на сервері.
Наприклад, сторінка зі списком товарів не потребує Client Component, якщо фільтрування та інші інтерактивні дії ще не реалізовані на клієнті:
// app/products/page.jsx
async function getProducts() {
const response = await fetch('https://api.example.com/products');
if (!response.ok) {
throw new Error('Не вдалося завантажити товари');
}
return response.json();
}
export default async function ProductsPage() {
const products = await getProducts();
return (
<main>
<h1>Товари</h1>
<ul>
{products.map((product) => (
<li key={product.id}>
{product.name} — {product.price} грн
</li>
))}
</ul>
</main>
);
}У цьому прикладі немає:
useState;
useEffect;
onClick;
доступу до браузера.
Тому додавати 'use client' не потрібно.
Якщо інтерфейс має змінюватися без нового запиту до сервера, зазвичай потрібен useState.
'use client';
import { useState } from 'react';
export default function QuantityControl() {
const [quantity, setQuantity] = useState(1);
return (
<div>
<button
type="button"
onClick={() => setQuantity((current) => Math.max(1, current - 1))}
>
−
</button>
<span>{quantity}</span>
<button
type="button"
onClick={() => setQuantity((current) => current + 1)}
>
+
</button>
</div>
);
}onClick і useState працюють у браузері, тому цей компонент має бути Client Component.
Обробники подій також потребують Client Component:
'use client';
export default function AlertButton() {
function handleClick() {
window.alert('Кнопку натиснуто');
}
return (
<button type="button" onClick={handleClick}>
Показати повідомлення
</button>
);
}Server Component може передати дані дочірньому Client Component, але не може сам містити клієнтський обробник події.
Хуки, призначені для клієнтського стану або життєвого циклу компонента, вимагають Client Component:
useState;
useEffect;
useReducer;
useRef для взаємодії з DOM;
інші хуки, які працюють із клієнтським станом або браузером.
Наприклад, читання ширини вікна:
'use client';
import { useEffect, useState } from 'react';
export default function WindowWidth() {
const [width, setWidth] = useState(null);
useEffect(() => {
function updateWidth() {
setWidth(window.innerWidth);
}
updateWidth();
window.addEventListener('resize', updateWidth);
return () => {
window.removeEventListener('resize', updateWidth);
};
}, []);
return <p>Ширина вікна: {width ?? 'визначається...'} px</p>;
}window доступний лише в браузері. Крім того, ширина вікна може змінюватися після завантаження сторінки, тому потрібні Client Component, useState і useEffect.
Такі об'єкти не існують під час виконання коду на сервері:
window;
document;
localStorage;
sessionStorage;
navigator;
Notification;
IntersectionObserver.
Якщо компонент використовує їх під час рендерингу або в ефекті, він має бути клієнтським.
Не потрібно перетворювати всю сторінку на Client Component через одну інтерактивну ділянку. Краще залишити сторінку серверною, а клієнтським зробити лише невеликий компонент.
app/
page.jsx
components/
LikeButton.jsx// app/page.jsx
import LikeButton from './components/LikeButton';
export default async function HomePage() {
const article = {
id: 1,
title: 'Як працюють Server Components',
excerpt: 'Короткий вступ до серверного рендерингу в Next.js.',
};
return (
<main>
<article>
<h1>{article.title}</h1>
<p>{article.excerpt}</p>
<LikeButton initialLikes={12} />
</article>
</main>
);
}// app/components/LikeButton.jsx
'use client';
import { useState } from 'react';
export default function LikeButton({ initialLikes }) {
const [likes, setLikes] = useState(initialLikes);
const [liked, setLiked] = useState(false);
function handleClick() {
setLiked((current) => !current);
setLikes((current) => (liked ? current - 1 : current + 1));
}
return (
<button type="button" onClick={handleClick} aria-pressed={liked}>
{liked ? 'Вам подобається' : 'Подобається'}: {likes}
</button>
);
}У цьому прикладі:
HomePage залишається Server Component;
дані статті формуються на сервері;
LikeButton відповідає лише за локальну інтерактивність;
число initialLikes передається з сервера в клієнтський компонент.
Такий підхід зменшує кількість JavaScript, який потрібно завантажити й виконати в браузері.
Після додавання 'use client' компонент не обов'язково буде вперше показаний лише після завантаження JavaScript.
Next.js може попередньо сформувати HTML для Client Component на сервері. Потім у браузері відбувається гідратація: React підключає до вже створеного HTML стан, обробники подій та іншу клієнтську логіку.
Тому Client Component:
може бути присутнім у початковому HTML;
завантажує клієнтський JavaScript;
стає інтерактивним після гідратації.
Через це код Client Component має бути сумісним із серверним попереднім рендерингом. Наприклад, пряме звернення до window під час рендерингу може спричинити помилку:
'use client';
export default function BrokenComponent() {
// Такий код виконується під час рендерингу
// і може виконатися не в браузері.
const width = window.innerWidth;
return <p>{width}px</p>;
}Безпечніший варіант — читати значення після монтування компонента в useEffect:
'use client';
import { useEffect, useState } from 'react';
export default function SafeComponent() {
const [width, setWidth] = useState(null);
useEffect(() => {
setWidth(window.innerWidth);
}, []);
return <p>{width === null ? 'Завантаження...' : `${width}px`}</p>;
}Директива 'use client' діє для конкретного файлу та його імпортів. Якщо файл позначено як Client Component, імпортовані ним компоненти також потрапляють до клієнтської частини дерева.
Тому директиву варто додавати якомога нижче в дереві компонентів.
Не варто робити клієнтською всю сторінку лише через кнопку в одному дочірньому компоненті:
// Не найкращий підхід
// app/page.jsx
'use client';
import LikeButton from './components/LikeButton';
export default function Page() {
// Уся сторінка тепер належить до клієнтської частини.
return <LikeButton />;
}Краще винести інтерактивність в окремий файл:
// app/page.jsx
import LikeButton from './components/LikeButton';
export default function Page() {
return (
<main>
<h1>Стаття</h1>
<p>Цей текст не потребує клієнтського JavaScript.</p>
<LikeButton initialLikes={12} />
</main>
);
}Server Component може передати Client Component звичайні серіалізовані дані через props:
// Server Component
import ProductCard from './ProductCard';
export default function ProductsPage() {
const product = {
id: 1,
name: 'Клавіатура',
price: 2500,
};
return <ProductCard product={product} />;
}// Client Component
'use client';
export default function ProductCard({ product }) {
return (
<article>
<h2>{product.name}</h2>
<p>{product.price} грн</p>
</article>
);
}Дані повинні мати формат, який можна передати між сервером і браузером: рядки, числа, булеві значення, масиви та прості об'єкти.
Не слід передавати як props функції, екземпляри класів або інші несеріалізовані значення. Обробник події потрібно оголосити всередині Client Component:
'use client';
export default function SaveButton() {
function handleSave() {
console.log('Збереження');
}
return (
<button type="button" onClick={handleSave}>
Зберегти
</button>
);
}Перед додаванням 'use client' перевірте компонент за такими питаннями:
Чи має компонент реагувати на onClick, onChange, onSubmit або інші події?
Чи потрібен useState або інший клієнтський хук?
Чи читає компонент window, document, localStorage або інший API браузера?
Чи має інтерфейс змінюватися без нового рендерингу сторінки на сервері?
Чи вимагає використана бібліотека клієнтського середовища?
Якщо на будь-яке питання відповідь «так», відповідна частина інтерфейсу, найімовірніше, має бути Client Component.
Якщо всі відповіді «ні», почніть із Server Component.
Практичне правило:
Спочатку створюйте компонент серверним і додавайте
'use client'лише тоді, коли з'являється конкретна клієнтська вимога.
| Сценарій | Потрібен Client Component? | |---|---| | Відображення списку даних | Ні | | Отримання даних у Server Component | Ні | | Кнопка зі зміною локального стану | Так | | Відкриття та закриття модального вікна | Так | | Перемикач теми через стан або localStorage | Так | | Обробка введення в пошукове поле на клієнті | Так | | Статичний заголовок і текст | Ні | | Відображення даних, переданих через props | Залежить від поведінки | | Використання window або document | Так |
'use client' до кожного компонентаЦе збільшує клієнтську частину застосунку без потреби. У результаті браузер завантажує більше JavaScript, а переваги Server Components використовуються гірше.
Виправлення: залишайте статичні та серверні частини серверними, а інтерактивні елементи ізолюйте в окремих Client Components.
useState без директивиimport { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}Такий файл має містити 'use client' перед імпортами:
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button type="button" onClick={() => setCount((value) => value + 1)}>
{count}
</button>
);
}window і document не слід читати безпосередньо під час рендерингу. Для коду, який має виконатися після появи компонента в браузері, використовуйте useEffect.
Інтерактивна кнопка не означає, що вся сторінка має бути клієнтською. Винесіть кнопку або віджет у менший Client Component і передайте йому необхідні дані.
Client Component не повинен напряму використовувати серверні секрети, серверні модулі або код, який розрахований лише на Node.js. Серверну роботу потрібно залишати на сервері, а клієнту передавати лише необхідні дані або доступний механізм взаємодії.
У Next.js компоненти за замовчуванням є Server Components.
Client Component позначається директивою 'use client'.
Він потрібен для стану, обробників подій, клієнтських хуків і браузерних API.
Server Component підходить для отримання даних і відображення контенту без локальної інтерактивності.
Не потрібно робити всю сторінку клієнтською через один інтерактивний елемент.
Розміщуйте 'use client' якомога ближче до компонента, якому справді потрібен браузер.
Client Component може бути попередньо відрендерений на сервері, а потім стати інтерактивним після гідратації.
Починайте із Server Component і додавайте клієнтську межу лише за конкретної потреби.