Пошук уроків, статей та іншого контенту
Зрозумійте роль Server Components у Next.js та рендерте компоненти на сервері без зайвого JavaScript у браузері.
Server Components — це компоненти React, які виконуються на сервері, а не в браузері користувача.
У Next.js з App Router компоненти за замовчуванням є серверними. Для них не потрібно додавати спеціальну директиву:
export default function Page() {
return <h1>Сторінка</h1>;
}Код такого компонента виконується під час побудови сторінки або на сервері під час запиту. Браузер отримує результат рендерингу, але сам код Server Component не надсилається до клієнта як JavaScript.
Це дає змогу:
зменшити розмір JavaScript у браузері;
отримувати дані безпосередньо на сервері;
працювати з базою даних або серверними API;
не відкривати клієнту секретні ключі;
швидше показувати початковий вміст сторінки.
Server Components не означають, що на сторінці взагалі не буде JavaScript. JavaScript усе одно може бути потрібен самому Next.js або клієнтським компонентам, якщо вони використовуються.
Усі файли в каталозі app за замовчуванням є Server Components:
app/
├── layout.tsx
├── page.tsx
└── products/
└── page.tsxНаприклад, app/products/page.tsx буде серверним компонентом:
export default function ProductsPage() {
return (
<main>
<h1>Товари</h1>
</main>
);
}У ньому можна виконувати серверну логіку, наприклад отримувати дані перед рендерингом компонента.
Створимо невеликий список товарів.
lib/products.tsexport type Product = {
id: number;
name: string;
price: number;
};
const products: Product[] = [
{
id: 1,
name: "Механічна клавіатура",
price: 3200,
},
{
id: 2,
name: "Бездротова миша",
price: 1800,
},
{
id: 3,
name: "USB-мікрофон",
price: 4500,
},
];
export async function getProducts(): Promise<Product[]> {
return products;
}app/products/page.tsximport { getProducts } from "@/lib/products";
export default async function ProductsPage() {
const products = await getProducts();
return (
<main>
<h1>Каталог товарів</h1>
<ul>
{products.map((product) => (
<li key={product.id}>
<strong>{product.name}</strong>
<span> — {product.price} грн</span>
</li>
))}
</ul>
</main>
);
}ProductsPage є асинхронним Server Component. Він:
виконується на сервері;
викликає getProducts;
створює HTML і дані для React;
надсилає браузеру результат.
Код функції getProducts і масив products не потрібні браузеру для відображення вже створеного списку.
У реальному застосунку getProducts може виконувати запит до бази даних або серверного API:
export async function getProducts() {
const response = await fetch("https://api.example.com/products");
if (!response.ok) {
throw new Error("Не вдалося отримати товари");
}
return response.json();
}Такий запит виконується на сервері, тому ключі доступу або інші серверні налаштування не потрібно передавати в браузер.
Server Components можуть бути асинхронними. Це особливо зручно для отримання даних:
export default async function ProfilePage() {
const profile = await getProfile();
return (
<section>
<h1>{profile.name}</h1>
<p>{profile.email}</p>
</section>
);
}Не потрібно завантажувати дані в useEffect лише для того, щоб показати початковий вміст сторінки. Дані можна отримати до рендерингу компонента на сервері.
Це відрізняється від типового клієнтського підходу:
"use client";
import { useEffect, useState } from "react";
export default function Profile() {
const [profile, setProfile] = useState(null);
useEffect(() => {
// Запит виконується вже в браузері
}, []);
return <div>Профіль</div>;
}Для початкового завантаження даних Server Component зазвичай простіший і не додає зайвого клієнтського JavaScript.
Серверні компоненти можуть:
бути асинхронними;
виконувати запити до серверних API;
звертатися до бази даних через серверний код;
читати серверне файлове сховище;
використовувати змінні середовища, призначені для сервера;
рендерити інші Server Components;
імпортувати Client Components.
Наприклад:
import { readFile } from "node:fs/promises";
export default async function AboutPage() {
const text = await readFile("content/about.txt", "utf-8");
return (
<main>
<h1>Про компанію</h1>
<p>{text}</p>
</main>
);
}Такий код має виконуватися тільки на сервері. Файл і виклик readFile не потрапляють у клієнтський bundle як логіка компонента.
Server Component не має браузерного стану та життєвого циклу. У ньому не можна використовувати:
useState;
useEffect;
інші React Hooks, що залежать від браузера;
window;
document;
обробники подій на кшталт onClick;
інтерактивну логіку, яка має виконуватися після завантаження сторінки.
Наприклад, цей код некоректний:
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}Для такого компонента потрібна директива "use client".
Якщо компонент має бути інтерактивним, його потрібно зробити Client Component:
"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" має бути на початку файлу, до імпортів.
Серверна сторінка може імпортувати клієнтський компонент:
import Counter from "@/components/Counter";
export default function HomePage() {
return (
<main>
<h1>Головна сторінка</h1>
<Counter />
</main>
);
}У цьому випадку:
HomePage залишається Server Component;
Counter стає Client Component;
JavaScript потрібен лише для інтерактивної частини;
статичний текст сторінки не потребує окремої клієнтської логіки.
Це називається межою Server/Client Components. Її варто розташовувати якомога нижче в дереві компонентів, щоб не перетворювати всю сторінку на клієнтську.
Server Component може передати дані Client Component через props:
import ProductActions from "@/components/ProductActions";
const product = {
id: 1,
name: "Механічна клавіатура",
price: 3200,
};
export default function ProductPage() {
return (
<main>
<h1>{product.name}</h1>
<p>{product.price} грн</p>
<ProductActions productId={product.id} />
</main>
);
}Клієнтський компонент:
"use client";
type ProductActionsProps = {
productId: number;
};
export default function ProductActions({
productId,
}: ProductActionsProps) {
function handleAddToCart() {
console.log(`Товар ${productId} додано до кошика`);
}
return <button onClick={handleAddToCart}>Додати до кошика</button>;
}Значення, які передаються через межу між сервером і клієнтом, мають бути серіалізованими. Зазвичай це:
рядки;
числа;
логічні значення;
масиви;
звичайні об’єкти;
null.
Не варто передавати як props функції, екземпляри класів або об’єкти з несумісною структурою.
Server Component може містити Client Component:
import SearchBox from "@/components/SearchBox";
export default function SearchPage() {
return (
<main>
<h1>Пошук товарів</h1>
<SearchBox />
</main>
);
}Після додавання "use client" до SearchBox клієнтською стає саме ця частина та її дочірні імпорти. Не весь батьківський компонент автоматично перетворюється на Client Component.
Водночас Client Component не може напряму імпортувати Server Component:
"use client";
import ServerProductList from "@/components/ServerProductList";
export default function Filters() {
return <ServerProductList />;
}Такий підхід порушує межу виконання: Client Component повинен працювати в браузері, а Server Component — на сервері.
Якщо потрібно вставити серверний вміст у клієнтський компонент, його можна передати через children із серверного батьківського компонента:
import Panel from "@/components/Panel";
import ProductList from "@/components/ProductList";
export default function ProductsPage() {
return (
<Panel>
<ProductList />
</Panel>
);
}У цьому прикладі ProductList може залишатися серверним, а Panel — клієнтським, якщо йому потрібна інтерактивність.
Server Component може бути виконаний:
під час побудови застосунку;
під час серверного запиту;
під час навігації, коли Next.js отримує дані для нового маршруту.
Точний момент залежить від маршруту, його даних і налаштувань кешування. Головна властивість Server Component полягає не лише в тому, коли він виконується, а в тому, що його логіка призначена для сервера й не надсилається браузеру як код клієнтського компонента.
Використовуйте Server Component, якщо компонент:
лише відображає дані;
отримує інформацію із сервера;
не має подій користувача;
не використовує стан або ефекти;
може бути повністю сформований на сервері.
Використовуйте Client Component, якщо компонент:
реагує на click, input, submit або інші події;
використовує useState чи useEffect;
працює з window або document;
потребує інтерактивної поведінки після завантаження сторінки.
У типовій сторінці корисно залишити більшу частину структури серверною, а інтерактивні елементи винести в невеликі Client Components.
"use client" до кожного компонентаЯкщо компонент не має інтерактивності, "use client" йому не потрібна. Надмірне використання директиви збільшує кількість JavaScript, який потрібно завантажити в браузер.
useEffect для всіх запитівЗапит у useEffect виконується після завантаження JavaScript у браузері. Для даних, необхідних під час першого рендерингу, краще спочатку розглянути асинхронний Server Component.
Цей код не працюватиме в Server Component:
export default function Theme() {
const theme = localStorage.getItem("theme");
return <p>Тема: {theme}</p>;
}localStorage доступний лише в браузері. Компонент, який використовує його, має бути Client Component.
Функцію не можна безпосередньо передати із Server Component до Client Component як звичайний prop:
<ClientButton onClick={() => console.log("Натиснуто")} />Обробник події має бути оголошений у Client Component, де він виконується в браузері.
Серверні ключі та секрети не можна імпортувати в Client Component. Логіку, яка використовує секретні значення, потрібно залишати на сервері.
У Next.js App Router компоненти за замовчуванням є Server Components.
Server Components виконуються на сервері, а їхній код не надсилається браузеру як клієнтський JavaScript.
Вони підходять для отримання даних, роботи із серверними ресурсами та рендерингу статичного або серверного вмісту.
useState, useEffect, браузерні API та обробники подій потребують Client Component.
"use client" створює межу, після якої компонент і його клієнтські імпорти працюють у браузері.
Найкраща практика — залишати сторінки серверними, а клієнтськими робити лише невеликі інтерактивні частини.