Пошук уроків, статей та іншого контенту
Визначите, як cookies, headers та інші Dynamic Functions змінюють поведінку кешування маршруту.
У Next.js маршрути App Router можуть бути:
статично згенерованими під час збірки;
динамічно відрендереними під час кожного запиту.
Dynamic Functions — це API, результат яких залежить від конкретного HTTP-запиту або його контексту. Найпоширеніші з них:
cookies();
headers();
draftMode();
useSearchParams() у Client Component;
searchParams у властивостях сторінки;
unstable_noStore() у версіях Next.js, де цей API доступний.
Коли серверний компонент використовує таку функцію, Next.js зазвичай не може заздалегідь підготувати однаковий HTML для всіх користувачів. Тому маршрут переходить до динамічного рендерингу.
Розглянемо звичайну сторінку:
export default function Page() {
return <h1>Публічна сторінка</h1>;
}Якщо сторінка не використовує дані запиту, Next.js може згенерувати її заздалегідь і зберігати результат у Full Route Cache.
Якщо ж сторінка читає cookie:
import { cookies } from 'next/headers';
export default async function Page() {
const cookieStore = await cookies();
const theme = cookieStore.get('theme')?.value ?? 'light';
return <p>Поточна тема: {theme}</p>;
}HTML залежить від cookie конкретного користувача. Один користувач може мати theme=light, а інший — theme=dark. Тому Next.js не може використовувати один статично збережений результат для всіх запитів.
У такому випадку маршрут стає динамічним:
сервер виконує компонент під час запиту;
читає cookie;
формує HTML для конкретного користувача;
результат маршруту не додається до Full Route Cache як загальний статичний HTML.
cookies()Функція cookies() дає доступ до cookie поточного запиту.
У сучасних версіях Next.js її потрібно викликати асинхронно:
import { cookies } from 'next/headers';
export default async function AccountPage() {
const cookieStore = await cookies();
const sessionId = cookieStore.get('session_id')?.value;
if (!sessionId) {
return <p>Ви не авторизовані.</p>;
}
return <p>Сторінка облікового запису</p>;
}Cookie часто використовують для:
ідентифікатора сесії;
мови інтерфейсу;
теми оформлення;
прапорців функцій;
налаштувань користувача.
Оскільки cookie може відрізнятися для кожного запиту, використання cookies() робить маршрут динамічним.
Методи cookies() схожі на роботу з Map:
const cookieStore = await cookies();
const token = cookieStore.get('token')?.value;
const allCookies = cookieStore.getAll();
const hasToken = cookieStore.has('token');Не слід передавати значення cookie в Client Component без потреби. Наприклад, токени сесії мають залишатися на сервері.
Cookie можна змінювати в Server Action або Route Handler. Читання cookie під час рендерингу та зміна cookie — різні операції.
Приклад Route Handler:
import { NextResponse } from 'next/server';
export async function POST() {
const response = NextResponse.json({ success: true });
response.cookies.set('theme', 'dark', {
httpOnly: true,
sameSite: 'lax',
secure: process.env.NODE_ENV === 'production',
path: '/',
});
return response;
}Після зміни cookie наступний запит до сторінки може отримати інший результат. Саме тому сторінка, яка читає цю cookie, повинна оброблятися з урахуванням запиту.
headers()Функція headers() повертає заголовки поточного HTTP-запиту.
import { headers } from 'next/headers';
export default async function Page() {
const requestHeaders = await headers();
const userAgent = requestHeaders.get('user-agent') ?? 'Невідомий браузер';
return (
<main>
<h1>Інформація про запит</h1>
<p>User-Agent: {userAgent}</p>
</main>
);
}Заголовки можуть відрізнятися між запитами. Наприклад, різні клієнти можуть надсилати різні значення user-agent, accept-language або власного заголовка авторизації.
Тому використання headers() також переводить маршрут у динамічний режим.
Не всі заголовки потрібно виводити в HTML. Деякі з них можуть містити чутливу інформацію.
import { headers } from 'next/headers';
export default async function Page() {
const requestHeaders = await headers();
const language = requestHeaders.get('accept-language') ?? 'uk';
const isUkrainian = language.toLowerCase().startsWith('uk');
return <p>{isUkrainian ? 'Вітаємо!' : 'Welcome!'}</p>;
}Заголовок використовується тільки на сервері, а в браузер передається вже готовий результат.
searchParamsПараметри URL після знака ? також можуть впливати на результат сторінки.
Наприклад, для URL /products?category=books сторінка може отримати category через властивість searchParams:
export default async function ProductsPage({ searchParams }) {
const params = await searchParams;
const category = params.category ?? 'all';
return (
<main>
<h1>Товари</h1>
<p>Категорія: {category}</p>
</main>
);
}У сучасних версіях Next.js searchParams є асинхронним значенням, тому його потрібно очікувати через await.
Результат сторінки залежить від URL запиту:
/products?category=books;
/products?category=games;
/products?category=music.
Через це сторінка не має одного універсального HTML-результату для всіх URL.
У Client Component для читання параметрів використовується useSearchParams():
'use client';
import { useSearchParams } from 'next/navigation';
export default function SearchInfo() {
const searchParams = useSearchParams();
const query = searchParams.get('q') ?? '';
return <p>Пошуковий запит: {query}</p>;
}useSearchParams() працює на клієнті. Він може вплинути на спосіб рендерингу частини дерева компонентів, але сам по собі не є заміною серверному читанню searchParams.
draftMode()draftMode() використовується для режиму попереднього перегляду контенту, який ще не опублікований.
import { draftMode } from 'next/headers';
export default async function Page() {
const { isEnabled } = await draftMode();
return (
<main>
<h1>Стаття</h1>
{isEnabled && <p>Режим попереднього перегляду увімкнено.</p>}
</main>
);
}Стан preview залежить від спеціального cookie запиту. Тому сторінка з draftMode() також має оброблятися динамічно.
У Next.js кешування не обмежується одним кешем. Важливо розрізняти:
Data Cache — кеш результатів отримання даних;
Full Route Cache — кеш готового результату маршруту;
Router Cache — кеш сегментів маршруту на стороні клієнта.
Dynamic Function насамперед впливає на Full Route Cache. Маршрут більше не може бути універсальним статичним результатом для всіх запитів.
Водночас це не обов'язково означає, що всі дані всередині маршруту перестають кешуватися.
Маршрут може бути динамічним, але окремий запит до API — кешованим:
import { cookies } from 'next/headers';
async function getPublicProducts() {
const response = await fetch('https://api.example.com/products', {
next: { revalidate: 60 },
});
if (!response.ok) {
throw new Error('Не вдалося завантажити товари');
}
return response.json();
}
export default async function ProductsPage() {
const cookieStore = await cookies();
const currency = cookieStore.get('currency')?.value ?? 'UAH';
const products = await getPublicProducts();
return (
<main>
<p>Валюта: {currency}</p>
<ul>
{products.map((product) => (
<li key={product.id}>
{product.name}: {product.price} {currency}
</li>
))}
</ul>
</main>
);
}У цьому прикладі:
cookies() робить маршрут динамічним;
значення currency може бути різним для кожного користувача;
список товарів може зберігатися в Data Cache протягом 60 секунд;
готовий HTML маршруту не кешується як єдиний статичний результат для всіх.
Це важлива відмінність: динамічний маршрут не обов'язково означає повну відмову від кешування даних.
fetchУ різних версіях Next.js автоматичні правила для fetch змінювалися. Тому для важливих запитів краще явно вказувати стратегію кешування.
const response = await fetch('https://api.example.com/data', {
cache: 'no-store',
});Такий запит виконується заново під час кожного серверного рендерингу.
Або можна задати час повторної перевірки:
const response = await fetch('https://api.example.com/data', {
next: {
revalidate: 300,
},
});Це дозволяє кешувати результат на 300 секунд.
Не варто покладатися лише на те, чи стоїть fetch до або після cookies() чи headers(). Явно задані параметри роблять намір зрозумілим і зменшують залежність від версії Next.js.
fetch з авторизацієюДані, що залежать від cookie або заголовка авторизації, зазвичай не можна спільно кешувати для всіх користувачів.
import { cookies } from 'next/headers';
async function getCurrentUser(token) {
const response = await fetch('https://api.example.com/me', {
headers: {
Authorization: `Bearer ${token}`,
},
cache: 'no-store',
});
if (!response.ok) {
return null;
}
return response.json();
}
export default async function ProfilePage() {
const cookieStore = await cookies();
const token = cookieStore.get('session_token')?.value;
if (!token) {
return <p>Потрібно увійти.</p>;
}
const user = await getCurrentUser(token);
if (!user) {
return <p>Сесію завершено.</p>;
}
return <h1>Вітаємо, {user.name}!</h1>;
}Для персональних даних використано cache: 'no-store'. Це запобігає повторному використанню відповіді одного користувача для іншого.
Іноді маршрут потрібно зробити динамічним навіть без прямого використання Dynamic Function. Для цього можна вказати:
export const dynamic = 'force-dynamic';Приклад:
export const dynamic = 'force-dynamic';
export default function Page() {
return <p>Цей маршрут завжди рендериться динамічно.</p>;
}Це налаштування застосовується до маршруту, навіть якщо його код наразі не читає cookie або заголовки.
Таке рішення варто використовувати, коли динамічна поведінка є вимогою маршруту. Якщо динамічність виникає природно через cookies() або headers(), зазвичай немає потреби дублювати її через dynamic = 'force-dynamic'.
Перевірити поведінку можна практично:
Запустіть застосунок у production-режимі.
Виконайте збірку через next build.
Перегляньте інформацію про маршрути у виводі збірки.
Зробіть кілька запитів із різними cookie або заголовками.
Переконайтеся, що персональні дані не змішуються між користувачами.
Під час розробки Next.js часто перебудовує сторінки, тому development-режим не завжди точно показує production-поведінку кешу.
Розглянемо повний приклад сторінки, яка:
читає cookie з мовою;
читає заголовок браузера;
отримує публічні дані з кешуванням;
формує персональний HTML.
import { cookies, headers } from 'next/headers';
async function getNews() {
const response = await fetch('https://api.example.com/news', {
next: {
revalidate: 120,
},
});
if (!response.ok) {
throw new Error('Не вдалося завантажити новини');
}
return response.json();
}
export default async function NewsPage() {
const cookieStore = await cookies();
const requestHeaders = await headers();
const language = cookieStore.get('language')?.value ?? 'uk';
const userAgent = requestHeaders.get('user-agent') ?? 'unknown';
const news = await getNews();
const title = language === 'uk' ? 'Останні новини' : 'Latest news';
return (
<main>
<h1>{title}</h1>
<p>Клієнт: {userAgent}</p>
<ul>
{news.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</main>
);
}Маршрут динамічний, оскільки його результат залежить від cookie та заголовка. Водночас список новин може повторно використовуватися з Data Cache протягом двох хвилин.
cookies() або headers() впливають на кешування результату маршруту, але не обов'язково забороняють кешування кожного fetch.
Для персональних даних використовуйте cache: 'no-store', а для публічних даних — явний revalidate, якщо це безпечно.
Небезпечно кешувати відповідь API, якщо вона залежить від токена або cookie користувача:
await fetch('https://api.example.com/profile', {
cache: 'no-store',
});Спільний кеш для персональних даних може призвести до витоку інформації між користувачами.
У сучасних версіях Next.js Dynamic APIs потрібно очікувати:
const cookieStore = await cookies();
const requestHeaders = await headers();Синхронна форма може зустрічатися у старих прикладах або підтримуватися тимчасово для сумісності, але для нового коду слід використовувати асинхронний API.
cookies() або headers() у Client Componentcookies() і headers() — серверні API. Вони не можуть безпосередньо використовуватися в компоненті з директивою 'use client'.
Правильний підхід:
прочитати значення у Server Component;
передати безпечне значення в Client Component через props.
Динамічність Full Route Cache і кешування даних — різні речі. Правильне рішення часто полягає в тому, щоб:
маршрут рендерити динамічно;
публічні дані кешувати;
приватні дані отримувати з cache: 'no-store'.
cookies() і headers() залежать від поточного HTTP-запиту.
Використання Dynamic Functions у серверному маршруті переводить його до динамічного рендерингу.
Динамічний маршрут не зберігається як один універсальний результат у Full Route Cache.
searchParams і draftMode() також можуть зробити результат маршруту залежним від запиту.
Динамічний маршрут усе ще може використовувати Data Cache для публічних даних.
Для персональних або авторизованих даних використовуйте cache: 'no-store'.
Для передбачуваної поведінки явно задавайте cache або next.revalidate.
У сучасних версіях Next.js cookies() і headers() потрібно викликати через await.