Пошук уроків, статей та іншого контенту
Додаватимете директиву "use client" і визначатимете, коли компоненту потрібні стан, обробники подій або браузерні API.
"use client"У Next.js з App Router компоненти за замовчуванням є серверними компонентами. Вони виконуються на сервері й не можуть безпосередньо використовувати:
useState, useReducer та інші клієнтські React-хуки;
обробники подій onClick, onChange, onSubmit;
браузерні API, наприклад window, document, localStorage;
useEffect та інші хуки, призначені для клієнтського виконання.
Щоб компонент міг використовувати ці можливості, на початку файлу додають директиву:
"use client";Вона має розташовуватися до імпортів і бути саме рядком верхнього рівня:
"use client";
import { useState } from "react";Після цього файл стає клієнтською межею. Компонент можна буде виконувати в браузері та використовувати клієнтські можливості React.
"use client"Додавайте директиву, якщо компонент:
має власний стан;
обробляє дії користувача;
використовує браузерні API;
використовує клієнтські React-хуки, наприклад useState або useEffect.
Без "use client" компонент не може викликати useState:
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Натиснуто: {count}
</button>
);
}Такий компонент спричинить помилку, оскільки він використовує і useState, і onClick.
Правильний варіант:
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button type="button" onClick={() => setCount(count + 1)}>
Натиснуто: {count}
</button>
);
}Тепер значення count зберігається в стані компонента, а натискання кнопки змінює цей стан.
Обробники подій виконуються в браузері, тому компонент з onClick, onChange або іншим обробником має бути клієнтським:
"use client";
export default function AlertButton() {
function handleClick() {
window.alert("Кнопку натиснуто");
}
return (
<button type="button" onClick={handleClick}>
Показати повідомлення
</button>
);
}У серверному компоненті можна відобразити кнопку, але не можна передати їй функцію-обробник:
export default function Page() {
function handleClick() {
// Цей обробник не може виконуватися як частина серверного компонента
console.log("Натиснуто");
}
return <button onClick={handleClick}>Натиснути</button>;
}У такій ситуації інтерактивну частину потрібно винести в окремий клієнтський компонент.
Браузерні об’єкти на кшталт window і localStorage недоступні під час серверного виконання.
"use client";
import { useEffect, useState } from "react";
export default function ThemePreference() {
const [theme, setTheme] = useState("light");
useEffect(() => {
const savedTheme = window.localStorage.getItem("theme");
if (savedTheme) {
setTheme(savedTheme);
}
}, []);
function handleThemeChange(event) {
const nextTheme = event.target.value;
setTheme(nextTheme);
window.localStorage.setItem("theme", nextTheme);
}
return (
<label>
Тема:
<select value={theme} onChange={handleThemeChange}>
<option value="light">Світла</option>
<option value="dark">Темна</option>
</select>
</label>
);
}"use client" дозволяє використовувати браузерні API в цьому файлі. Звернення до localStorage розташоване в useEffect, тому воно відбувається після монтування компонента в браузері.
У типовому застосунку Next.js сторінка може залишатися серверним компонентом, а інтерактивну частину можна винести в клієнтський компонент.
Файл app/components/Counter.jsx:
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<section>
<p>Поточне значення: {count}</p>
<button type="button" onClick={() => setCount(count + 1)}>
Збільшити
</button>
</section>
);
}Файл app/page.jsx:
import Counter from "./components/Counter";
export default function Page() {
return (
<main>
<h1>Лічильник</h1>
<p>Цей текст відображається серверним компонентом.</p>
<Counter />
</main>
);
}Сторінка не потребує "use client", оскільки сама не використовує стан або обробники подій. Вона імпортує клієнтський компонент і відображає його.
Це дає змогу залишати більшу частину інтерфейсу серверною, а клієнтською робити лише необхідну інтерактивну частину.
Директива "use client" визначає межу, з якої починається клієнтська частина дерева імпортів.
Page
└── ProductList
└── ProductFilter ("use client")Якщо файл ProductFilter має "use client", його клієнтські залежності також потрапляють до клієнтської частини застосунку.
Тому не потрібно додавати директиву до кожного компонента без винятку. Краще розміщувати її на найменшому компоненті, якому справді потрібна інтерактивність.
Наприклад, якщо лише поле пошуку має стан, не обов’язково робити всю сторінку клієнтською:
import SearchInput from "./components/SearchInput";
export default function ProductsPage() {
return (
<main>
<h1>Товари</h1>
<p>Список товарів завантажено сервером.</p>
<SearchInput />
</main>
);
}Файл app/components/SearchInput.jsx:
"use client";
import { useState } from "react";
export default function SearchInput() {
const [query, setQuery] = useState("");
return (
<label>
Пошук:
<input
type="search"
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Введіть назву"
/>
<span> Запит: {query || "—"}</span>
</label>
);
}Серверний компонент може передати клієнтському звичайні дані через props:
import ProductCard from "./components/ProductCard";
export default function ProductsPage() {
const product = {
id: 1,
name: "Механічна клавіатура",
price: 2500,
};
return (
<main>
<ProductCard product={product} />
</main>
);
}Файл app/components/ProductCard.jsx:
"use client";
export default function ProductCard({ product }) {
return (
<article>
<h2>{product.name}</h2>
<p>{product.price} грн</p>
</article>
);
}Дані, які передаються через межу між серверним і клієнтським компонентами, мають бути серіалізованими. На практиці це звичайні:
рядки;
числа;
логічні значення;
масиви;
об’єкти з такими значеннями.
Функції не слід передавати як звичайні props із серверного компонента до клієнтського:
// Такий підхід не підходить для межі серверного і клієнтського компонентів
<ProductCard onSelect={handleSelect} />;Клієнтський компонент має самостійно визначити обробник події або отримати серіалізовані дані, необхідні для його роботи.
"use client" не означає «виконувати тільки в браузері»Клієнтський компонент усе одно може бути попередньо відрендерений на сервері під час формування початкової сторінки. Директива означає, що компонент також може бути виконаний у браузері та мати інтерактивність.
Тому під час початкового відображення не слід безпосередньо використовувати браузерні об’єкти під час формування JSX:
"use client";
export default function CurrentWidth() {
return <p>{window.innerWidth}px</p>;
}Такий код може спричинити помилку під час серверного попереднього рендерингу, оскільки window на сервері не існує.
Безпечніший варіант — прочитати значення після монтування:
"use client";
import { useEffect, useState } from "react";
export default function CurrentWidth() {
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>;
}Поки компонент не змонтувався в браузері, він показує початкове значення. Після цього useEffect отримує ширину вікна та оновлює стан.
Перед додаванням "use client" перевірте компонент:
Чи використовує він useState або інший клієнтський хук?
Чи містить він onClick, onChange, onSubmit або інші обробники?
Чи звертається до window, document, localStorage або іншого браузерного API?
Чи має він виконувати логіку після монтування в браузері?
Якщо відповідь «так» хоча б на одне запитання, компонент, імовірно, має бути клієнтським.
Якщо компонент лише:
отримує дані;
формує розмітку;
відображає текст;
не реагує на дії користувача;
то "use client" йому, найімовірніше, не потрібна.
Неправильно:
import { useState } from "react";
"use client";Правильно:
"use client";
import { useState } from "react";Потрібно використовувати саме:
"use client";Не слід поміщати її всередину функції або умовного блоку.
"use client" до всієї сторінки без потребиЯкщо інтерактивним є лише один невеликий елемент, краще зробити клієнтським тільки його. Це допомагає зменшити клієнтську частину застосунку й чіткіше розділити відповідальність компонентів.
window під час першого рендерингуНеправильно:
"use client";
const language = window.navigator.language;Клієнтський компонент може бути попередньо виконаний на сервері, де window відсутній. Браузерну логіку потрібно виконувати після монтування або в обробнику події.
Серверний компонент не може передати звичайну функцію як props клієнтському компоненту. Передавайте дані, які можна серіалізувати, а обробники визначайте в клієнтському компоненті.
Компоненти Next.js за замовчуванням є серверними.
"use client" додають на початку файлу, перед імпортами.
Директива потрібна для стану, обробників подій, браузерних API та клієнтських React-хуків.
Серверний компонент може імпортувати й відображати клієнтський компонент.
Краще робити клієнтською лише найменшу частину інтерфейсу, якій потрібна інтерактивність.
"use client" не гарантує виконання лише в браузері: початковий рендеринг може відбуватися на сервері.
Дані через межу серверного й клієнтського компонентів мають бути серіалізованими.