1. Введение в Client Components
Если Server Components — это такие «тихие интроверты», которые любят работать на сервере и не лезут в браузер, то Client Components — настоящие «души компании», которые живут в браузере, слушают пользователя и делают приложение интерактивным.
Client Component — это React-компонент, который выполняется на клиенте (в браузере пользователя). Такой компонент может использовать React-хуки (useState, useEffect и т.д.), напрямую работать с DOM, слушать события и, конечно же, обновлять интерфейс в ответ на действия пользователя.
В Next.js 13+ (и особенно в App Router) все компоненты по умолчанию считаются Server Components. Чтобы сделать компонент клиентским, нужно явно это указать.
Как объявить Client Component?
Очень просто: на самой первой строке файла компонента пишется директива "use client" (строго в кавычках, без запятых, без export, без скобок):
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Кликнули {count} раз
</button>
);
}
Важно: Директива "use client" должна стоять первой строкой в файле. Если добавить её ниже — Next.js просто не поймёт, что вы хотели.
2. Когда нужны Client Components?
В мире Server Components всё хорошо, пока вам не понадобится интерактивность. Но как только появляется задача реагировать на действия пользователя — без Client Components не обойтись.
Примеры ситуаций, когда нужны Client Components:
- Кнопка, которая увеличивает счётчик (useState)
- Форма с валидацией на клиенте
- Выпадающее меню, модальное окно, табы
- Анимации, которые зависят от действий пользователя
- Интеграция с браузерными API (например, localStorage, window, document)
- Любые сторонние React-библиотеки, которые требуют хуков или эффектов (например, react-select, react-datepicker и т.д.)
Пример: простой счётчик
"use client";
import { useState } from "react";
export default function Counter() {
const [value, setValue] = useState(0);
return (
<div>
<p>Значение: {value}</p>
<button onClick={() => setValue(value + 1)}>+1</button>
</div>
);
}
Пример: форма с валидацией
"use client";
import { useState } from "react";
export default function SimpleForm() {
const [name, setName] = useState("");
const [error, setError] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (name.length < 3) {
setError("Имя слишком короткое!");
} else {
setError("");
alert("Отправлено!");
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={e => setName(e.target.value)}
placeholder="Введите имя"
/>
<button>Отправить</button>
{error && <div style={{color: "red"}}>{error}</div>}
</form>
);
}
3. Как работает "use client" и смешивание компонентов
Почему нельзя просто использовать хуки в Server Components?
Server Components не попадают в клиентский бандл, они не знают о браузере и не могут хранить состояние между рендерами на клиенте. Если попытаться использовать, например, useState или useEffect в Server Component — получите ошибку: "React Server Components does not support useState" (и это не баг, а фича).
Как Next.js отличает Client и Server Components?
- Server Component: нет директивы "use client" — исполняется только на сервере, не может использовать хуки.
- Client Component: есть директива "use client" — исполняется на клиенте, может использовать хуки, работать с DOM, слушать события.
Аналогия: Представьте, что у вас есть два типа работников: одни сидят в офисе (сервер), другие работают "в полях" (клиент). Офисные работники могут делать отчёты, но не могут общаться с клиентами напрямую. А вот "полевые" могут реагировать на запросы клиентов, но иногда им нужны данные из офиса.
Можно ли вкладывать компоненты друг в друга?
- Server Component может рендерить другие Server Components и Client Components.
- Client Component может рендерить только Client Components (или обычные React-компоненты без "use client", которые не используют серверные возможности).
Важно: Если Server Component импортирует Client Component, то только дочерний компонент станет клиентским, а родитель останется серверным. Это позволяет делать гибридные страницы: большая часть рендерится на сервере, а только маленькие "островки" интерактивности становятся клиентскими.
Схема смешивания компонентов
[Server Component]
├── [Server Component]
├── [Client Component] ("use client")
│ ├── [Client Component]
│ └── [...]
└── [Server Component]
4. Практика: внедряем интерактивность в приложение
В рамках вашего учебного приложения (например, список задач) мы можем добавить интерактивный компонент — например, фильтр задач или кнопку "Добавить задачу".
Пример: Фильтр задач (TaskFilter)
"use client";
import { useState } from "react";
export default function TaskFilter({ onFilter }) {
const [query, setQuery] = useState("");
function handleChange(e) {
setQuery(e.target.value);
onFilter(e.target.value);
}
return (
<input
type="text"
placeholder="Поиск по задачам..."
value={query}
onChange={handleChange}
/>
);
}
Как использовать TaskFilter в Server Component
// app/tasks/page.jsx (Server Component)
import TaskFilter from "./TaskFilter"; // Client Component
export default async function TasksPage() {
// Загрузка задач с сервера
const tasks = await getTasks();
// Здесь нельзя использовать useState напрямую,
// но можно передать callback в клиентский компонент
return (
<div>
<h1>Список задач</h1>
<TaskFilter onFilter={query => { /* фильтрация задач */ }} />
{/* Список задач */}
</div>
);
}
Обратите внимание: Server Component может использовать Client Component, но не наоборот! Если вы попытаетесь импортировать Server Component внутрь Client Component — получите ошибку.
5. Полезные нюансы
Особенности Client Components
Преимущества:- Интерактивность: хуки, обработка событий, доступ к браузерным API.
- Изоляция интерактивности: можно делать только нужные части страницы клиентскими, остальное — серверным (это экономит размер бандла и ускоряет загрузку).
- Совместимость с React-библиотеками: если библиотека требует хуки или эффекты, используйте её только внутри Client Component.
- Больше JavaScript на клиенте: всё, что находится внутри Client Component, попадает в бандл (и скачивается пользователем).
- Нет доступа к серверным функциям: нельзя напрямую вызывать серверные API, читать из базы данных и т.п.
- Нельзя импортировать Server Component: если нужен серверный код, делайте его "снаружи" и передавайте данные через пропсы.
Где использовать "use client"
- Только в самом верху файла компонента (до любых импортов, комментариев и т.д.).
- Только в файлах, которые действительно должны быть интерактивными.
- Не нужно писать "use client" в каждом файле — используйте только для "островков" интерактивности.
Как понять, что компонент должен быть клиентским?
Задайте себе вопросы:
- Использую ли я хуки (useState, useEffect и т.п.)?
- Работаю ли я с DOM напрямую?
- Слушаю ли я события пользователя?
- Использую ли я стороннюю React-библиотеку, которая требует хуки?
Если хотя бы на один вопрос ответ "да" — компонент должен быть клиентским.
Можно ли смешивать Client и Server Components?
Да! Это и есть главная фишка Next.js 15. Вы можете строить страницы из "больших кусков" серверного рендера, а интерактивные элементы делать маленькими клиентскими компонентами. Такой подход называется islands architecture («архитектура островков»).
6. Типичные ошибки и нюансы
Ошибка №1: забыли написать "use client" — не работает интерактивность.
Если вы используете хуки, но забыли директиву, получите ошибку: "React Server Components does not support useState". Просто добавьте "use client" в начало файла.
Ошибка №2: написали "use client" везде — страница стала "толстой" и медленной.
Не стоит делать всё приложение клиентским! Это убивает преимущества Server Components. Делайте клиентскими только то, что реально должно реагировать на пользователя.
Ошибка №3: пытаетесь импортировать Server Component в Client Component.
Это не сработает: клиентский компонент не может напрямую использовать серверные. Если нужно получить серверные данные — делайте загрузку данных выше по иерархии (в серверном компоненте) и передавайте их через пропсы.
Ошибка №4: используете серверные API (например, чтение из базы данных) внутри Client Component.
Такое не сработает: в клиентском компоненте нет доступа к серверу напрямую. Для этого используйте Server Actions, API-эндпоинты или передавайте данные через пропсы.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ