Пошук уроків, статей та іншого контенту
Читайте заголовки запиту та формуйте заголовки відповіді в серверних частинах Next.js.
HTTP-заголовки передають додаткову інформацію разом із запитом і відповіддю.
Клієнт надсилає заголовки запиту:
GET /api/profile HTTP/1.1
Host: example.com
Authorization: Bearer token
Accept-Language: ukСервер формує заголовки відповіді:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
X-Request-ID: abc-123У Next.js заголовки можна:
прочитати із запиту;
додати або змінити у відповіді;
використати під час роботи Server Components, Route Handlers і Middleware.
Для роботи із заголовками використовується стандартний Web API Headers.
У App Router заголовки поточного запиту можна прочитати за допомогою headers з next/headers.
Сучасна версія Next.js використовує асинхронний API, тому headers() потрібно очікувати через await.
// app/account/page.jsx
import { headers } from 'next/headers';
export default async function AccountPage() {
const requestHeaders = await headers();
const userAgent = requestHeaders.get('user-agent');
const language = requestHeaders.get('accept-language');
return (
<main>
<h1>Профіль</h1>
<p>User-Agent: {userAgent ?? 'Невідомо'}</p>
<p>Мова: {language ?? 'Невідомо'}</p>
</main>
);
}Метод get() повертає:
значення заголовка як рядок;
null, якщо заголовок відсутній.
Назви HTTP-заголовків нечутливі до регістру:
requestHeaders.get('User-Agent');
requestHeaders.get('user-agent');
requestHeaders.get('USER-AGENT');Усі три варіанти звертаються до одного заголовка. Водночас зазвичай використовують стандартний запис у нижньому регістрі.
Для перевірки можна використати метод has():
import { headers } from 'next/headers';
export default async function AdminPage() {
const requestHeaders = await headers();
const hasAuthorization = requestHeaders.has('authorization');
return (
<main>
{hasAuthorization ? (
<p>Заголовок авторизації передано.</p>
) : (
<p>Заголовок авторизації відсутній.</p>
)}
</main>
);
}Наявність заголовка не означає, що його значення коректне. Наприклад, сам факт наявності Authorization не підтверджує особу користувача. Значення потрібно перевіряти на сервері.
headers() залежить від конкретного HTTP-запиту. Тому сторінка, яка читає заголовки, не може бути повністю статичною: Next.js розглядає її як динамічну.
import { headers } from 'next/headers';
export default async function Page() {
const requestHeaders = await headers();
const requestId = requestHeaders.get('x-request-id');
return <p>Ідентифікатор запиту: {requestId ?? 'відсутній'}</p>;
}Значення x-request-id може відрізнятися для кожного запиту, тому результат не можна підготувати один раз під час збірки.
У Route Handler заголовки доступні через об'єкт Request. Це стандартний API платформи:
// app/api/request-info/route.js
export async function GET(request) {
const userAgent = request.headers.get('user-agent');
const language = request.headers.get('accept-language');
return Response.json({
userAgent,
language,
});
}Тепер GET-запит до /api/request-info поверне інформацію із заголовків запиту.
Об'єкт request.headers має ті самі основні методи:
request.headers.get('authorization');
request.headers.has('content-type');
request.headers.entries();Наприклад, можна перевірити тип даних запиту:
// app/api/data/route.js
export async function POST(request) {
const contentType = request.headers.get('content-type');
if (!contentType?.includes('application/json')) {
return Response.json(
{ error: 'Очікується JSON-запит' },
{ status: 415 }
);
}
const body = await request.json();
return Response.json({
received: body,
});
}Перевірка типу даних не замінює валідацію самого JSON. Сервер також має перевіряти структуру та значення отриманих даних.
Заголовки відповіді можна передати через параметр headers конструктора Response:
// app/api/health/route.js
export async function GET() {
return Response.json(
{
status: 'ok',
},
{
headers: {
'Cache-Control': 'no-store',
'X-Service-Version': '1.0.0',
},
}
);
}Next.js збереже ці заголовки у HTTP-відповіді.
Для повного контролю можна спочатку створити відповідь, а потім змінити її headers:
// app/api/response-info/route.js
export async function GET() {
const response = Response.json({
message: 'Готово',
});
response.headers.set('X-Request-Source', 'api');
response.headers.set('Cache-Control', 'no-store');
return response;
}Методи заголовків відповіді:
set(name, value) — встановлює значення;
append(name, value) — додає ще одне значення;
delete(name) — видаляє заголовок;
get(name) — читає встановлене значення.
NextResponseУ Route Handlers можна використовувати як стандартний Response, так і NextResponse з next/server.
NextResponse зручний, коли потрібно явно керувати відповіддю Next.js:
// app/api/profile/route.js
import { NextResponse } from 'next/server';
export async function GET(request) {
const requestId = request.headers.get('x-request-id') ?? crypto.randomUUID();
const response = NextResponse.json({
authenticated: Boolean(request.headers.get('authorization')),
});
response.headers.set('X-Request-ID', requestId);
response.headers.set('Cache-Control', 'no-store');
return response;
}У цьому прикладі:
сервер читає x-request-id із запиту;
якщо його немає, створює новий ідентифікатор;
повертає його у заголовку X-Request-ID;
забороняє кешування відповіді.
Повний приклад Route Handler із перевіркою запиту:
// app/api/profile/route.js
import { NextResponse } from 'next/server';
export async function GET(request) {
const authorization = request.headers.get('authorization');
const requestId = request.headers.get('x-request-id') ?? crypto.randomUUID();
const responseBody = {
authenticated: Boolean(authorization),
requestId,
};
const response = NextResponse.json(responseBody);
response.headers.set('X-Request-ID', requestId);
response.headers.set('Cache-Control', 'no-store');
return response;
}Це не є повноцінною автентифікацією: код лише перевіряє наявність заголовка. Для реальної автентифікації значення токена потрібно перевірити.
У Middleware заголовки запиту доступні через request.headers, а заголовки відповіді можна встановити через NextResponse.
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
const requestId = request.headers.get('x-request-id') ?? crypto.randomUUID();
const response = NextResponse.next();
response.headers.set('X-Request-ID', requestId);
return response;
}
export const config = {
matcher: ['/api/:path*'],
};Цей Middleware виконується для маршрутів /api/... і додає до відповіді заголовок X-Request-ID.
Middleware також може передати змінений заголовок далі до Route Handler або Server Component. Для цього заголовки запиту потрібно передати до NextResponse.next():
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-middleware-source', 'edge');
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}У подальшому серверному коді цей заголовок можна прочитати:
// app/api/source/route.js
export async function GET(request) {
const source = request.headers.get('x-middleware-source');
return Response.json({
source,
});
}Важливо розрізняти:
request: { headers } змінює заголовки запиту, який передається далі;
response.headers.set(...) змінює заголовки відповіді, яку отримає клієнт.
Іноді серверний код викликає інший API. Якщо зовнішній сервіс має отримати певний заголовок, його потрібно передати явно:
// app/api/weather/route.js
export async function GET(request) {
const language = request.headers.get('accept-language') ?? 'uk';
const externalResponse = await fetch('https://example.test/weather', {
headers: {
'Accept-Language': language,
},
});
if (!externalResponse.ok) {
return Response.json(
{ error: 'Не вдалося отримати погоду' },
{ status: 502 }
);
}
const weather = await externalResponse.json();
return Response.json(weather);
}Заголовки вхідного запиту не передаються до іншого fetch автоматично. Сервер повинен явно вибрати, які заголовки можна переслати.
Не слід бездумно пересилати всі заголовки. Серед них можуть бути:
токени доступу;
внутрішні службові значення;
заголовки, призначені лише для поточного сервера.
Заголовки на кшталт x-forwarded-for, x-forwarded-proto або x-forwarded-host часто додають проксі-сервери та балансувальники.
Їхнє значення залежить від конфігурації інфраструктури. Не варто використовувати їх для критичних рішень без перевірки, що запит справді пройшов через довірений проксі.
Наприклад, значення x-forwarded-for не слід автоматично вважати надійною IP-адресою користувача:
export async function GET(request) {
const forwardedFor = request.headers.get('x-forwarded-for');
return Response.json({
forwardedFor,
});
}Це лише читання значення, а не доказ ідентичності клієнта.
headers(), а коли request.headersВибір залежить від місця виконання коду:
у Server Component використовуйте await headers() з next/headers;
у Route Handler використовуйте request.headers;
у Middleware використовуйте request.headers;
для заголовків відповіді використовуйте Response, NextResponse або їхній об'єкт headers.
Для Route Handler зазвичай краще використовувати вже доступний request, а не headers():
// app/api/example/route.js
export async function GET(request) {
const value = request.headers.get('x-example');
return Response.json({ value });
}Так залежність від вхідного запиту явно видна у параметрах функції.
headers()Об'єкт, який повертає headers(), призначений для читання:
import { headers } from 'next/headers';
export default async function Page() {
const requestHeaders = await headers();
// Так робити не потрібно:
// requestHeaders.set('x-example', 'value');
return <p>{requestHeaders.get('x-example')}</p>;
}Щоб встановити заголовок відповіді, використовуйте Route Handler або Middleware.
awaitУ сучасному Next.js headers() є асинхронною функцією:
import { headers } from 'next/headers';
export default async function Page() {
const requestHeaders = await headers();
return <p>{requestHeaders.get('user-agent')}</p>;
}Не слід звертатися до результату як до вже готового синхронного об'єкта.
nullЗаголовок може не бути переданий:
const token = request.headers.get('authorization');
if (!token) {
return Response.json(
{ error: 'Потрібна авторизація' },
{ status: 401 }
);
}Не використовуйте значення заголовка без перевірки, якщо воно не є обов'язковим.
Ці два рядки працюють у різних напрямках:
const clientHeader = request.headers.get('x-client-value');
response.headers.set('x-server-value', 'result');Перший читає значення від клієнта. Другий додає значення, яке отримає клієнт.
Клієнт може самостійно надіслати більшість звичайних заголовків. Не використовуйте такі значення як доказ ролі, прав або особи користувача:
const role = request.headers.get('x-user-role');
// Небезпечно: клієнт може сам надіслати x-user-role: admin
if (role === 'admin') {
// ...
}Права доступу потрібно визначати на сервері після перевірки автентифікації.
Заголовки запиту читаються через стандартний API Headers.
У Server Components використовуйте await headers() з next/headers.
У Route Handlers і Middleware заголовки запиту доступні через request.headers.
Заголовки відповіді встановлюються через Response або NextResponse.
get() може повернути null, тому відсутні заголовки потрібно обробляти.
Читання заголовків робить сторінку залежною від конкретного запиту та динамічною.
Не змінюйте об'єкт заголовків, отриманий через headers().
Не вважайте значення від клієнта надійним без серверної перевірки.