Пошук уроків, статей та іншого контенту
Чому за замовчуванням компоненти в Next.js виконуються тільки на сервері — і коли потрібен виняток.
У App Router кожен компонент за замовчуванням — Server Component: він виконується лише на сервері під час рендерингу сторінки, а на клієнт відправляється вже готовий HTML — жодного JavaScript-коду цього компонента у клієнтський бандл не потрапляє. Це протилежність до «класичного» React SPA, де кожен компонент виконується (і повторно рендериться) у браузері.
Звертатись напряму до бази даних чи файлової системи, без окремого API-шару — код виконується на сервері, тож має прямий доступ до серверних ресурсів.
Використовувати секрети (API-ключі, токени доступу до бази даних) безпосередньо в коді компонента — вони ніколи не потрапляють у код, що бачить браузер.
Бути async-функцією та напряму await-ити дані — без useEffect/useState для початкового завантаження.
Server Component не може використовувати стан, ефекти, обробники подій чи будь-які браузерні API (localStorage, window) — усе це вимагає виконання в браузері. Щоб отримати такі можливості, компонент явно позначають директивою "use client" на самому початку файлу:
"use client"; // без цього рядка useState тут викличе помилку
import { useState } from "react";
export function LikeButton() {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>{liked ? "❤️" : "🤍"}</button>;
}Типовий патерн — Server Component отримує дані й рендерить сторінку, а невеликі острівці інтерактивності (кнопка лайка, випадаюче меню, форма з валідацією на льоту) виносяться в окремі Client Component і вставляються всередину:
// app/posts/[slug]/page.tsx — Server Component (без "use client")
async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug); // пряме звернення до бази даних на сервері
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
<LikeButton /> {/* Client Component усередині Server Component */}
</article>
);
}Обернений напрямок — імпорт Server Component напряму всередину Client Component — неможливий: щойно файл позначено "use client", усе дерево компонентів під ним теж стає клієнтським. Щоб передати серверний вміст усередину клієнтської обгортки, його передають через children ззовні, а не імпортують напряму.
Кожен компонент, позначений "use client", і все, що він імпортує, потрапляє в JavaScript-бандл, який завантажує браузер. Тримання межі "use client" якомога вужчою — довкола справді інтерактивних частин, а не навколо всієї сторінки — напряму зменшує обсяг JS, який браузеру потрібно завантажити й виконати.
Ставити "use client" на верхньому рівні застосунку «про всяк випадок» — це вимикає переваги Server Components для всього дерева під цим файлом, хоча інтерактивність зазвичай потрібна лише невеликій частині UI.
Використання useState/useEffect/onClick у файлі без директиви "use client" — компіляція чи рантайм видасть явну помилку, що ці можливості недоступні в Server Component.
Спроба імпортувати Server Component напряму всередину Client Component — потрібно передавати серверний вміст через children ззовні, а не імпортом усередині клієнтського файлу.
У App Router компоненти за замовчуванням — Server Components: виконуються лише на сервері, мають прямий доступ до бази даних і секретів, і не додають JavaScript у клієнтський бандл. Директива "use client" явно позначає межу, за якою компонент і все дерево під ним стають клієнтськими та отримують доступ до стану, ефектів і браузерних API — цю межу варто тримати якомога вужчою.