Навигация

Next.js Server API: headers, cookies, cache

Next.js App Router предоставляет набор серверных и клиентских API для работы с запросом, навигацией и кэшированием. Правильный выбор между headers(), cookies(), redirect() и хуками useRouter() определяет архитектуру страницы и её производительность. В статье разберём синтаксис, примеры и ограничения каждой функции, а также соберём их в сводную таблицу.

Содержание

  1. next/headers
  2. next/navigation
  3. next/cache
  4. Клиентские хуки
  5. Сводная таблица
  6. Лучшие практики
  7. Динамический рендеринг
  8. Ключевые тезисы для интервью
  9. Заключение
  10. Полезные ссылки

next/headers

headers()

Назначение: Чтение HTTP заголовков входящего запроса в Server Components.

Импорт:

import { headers } from 'next/headers'

Пример использования:

import { headers } from 'next/headers'

export default async function Page() {
  const headersList = await headers()
  const userAgent = headersList.get('user-agent')
  const authorization = headersList.get('authorization')
  
  return <div>User Agent: {userAgent}</div>
}

Важно:

  • Асинхронная функция (начиная с Next.js 15)
  • Возвращает read-only объект Headers
  • Нельзя изменять заголовки (нет методов set или delete)
  • Использование делает маршрут динамическим (отключает статическую генерацию)

Доступные методы:

  • get(name) - получить значение заголовка
  • has(name) - проверить наличие заголовка
  • entries() - итератор всех пар ключ/значение
  • keys() - итератор ключей
  • values() - итератор значений
  • forEach() - выполнить функцию для каждой пары

cookies()

Назначение: Чтение и запись HTTP cookies в Server Components, Server Functions и Route Handlers.

Импорт:

import { cookies } from 'next/headers'

Примеры:

import { cookies } from 'next/headers'

export default async function Page() {
  const cookieStore = await cookies()
  const theme = cookieStore.get('theme')
  
  return <div>Current theme: {theme?.value}</div>
}
'use server'

import { cookies } from 'next/headers'

export async function setTheme(theme: string) {
  const cookieStore = await cookies()
  cookieStore.set('theme', theme, { 
    secure: true,
    httpOnly: true,
    maxAge: 60 * 60 * 24 * 30 // 30 дней
  })
}
'use server'

import { cookies } from 'next/headers'

export async function deleteTheme() {
  const cookieStore = await cookies()
  cookieStore.delete('theme')
}

Важно:

  • Асинхронная функция (начиная с Next.js 15)
  • В Server Components можно только читать cookies
  • В Server Functions и Route Handlers можно читать и писать
  • Использование делает маршрут динамическим

Доступные методы:

  • get(name) - получить cookie по имени
  • getAll() - получить все cookies
  • has(name) - проверить наличие cookie
  • set(name, value, options) - установить cookie
  • delete(name) - удалить cookie

Опции при установке cookie:

  • expires - дата истечения
  • maxAge - время жизни в секундах
  • domain - домен
  • path - путь (по умолчанию /)
  • secure - только HTTPS
  • httpOnly - недоступна для JavaScript
  • sameSite - контроль кросс-доменных запросов (lax, strict, none)

draftMode()

Назначение: Управление режимом черновика для предпросмотра неопубликованного контента из CMS.

Импорт:

import { draftMode } from 'next/headers'

Примеры:

Проверка статуса:

import { draftMode } from 'next/headers'

export default async function Page() {
  const { isEnabled } = await draftMode()
  
  return (
    <div>
      Draft Mode is {isEnabled ? 'Enabled' : 'Disabled'}
    </div>
  )
}

Включение режима (в Route Handler):

import { draftMode } from 'next/headers'

export async function GET() {
  const draft = await draftMode()
  draft.enable()
  return new Response('Draft mode enabled')
}

Выключение режима:

import { draftMode } from 'next/headers'

export async function GET() {
  const draft = await draftMode()
  draft.disable()
  return new Response('Draft mode disabled')
}

Важно:

  • Асинхронная функция (начиная с Next.js 15)
  • Работает через cookie __prerender_bypass
  • При включении обходит кэширование
  • Новое значение cookie генерируется при каждом next build

next/navigation

redirect()

Назначение: Перенаправление пользователя на другой URL.

Импорт:

import { redirect } from 'next/navigation'

Пример:

import { redirect } from 'next/navigation'

export default async function Page({ params }) {
  const { id } = await params
  const data = await fetchData(id)
  
  if (!data) {
    redirect('/login')
  }
  
  return <div>{data.title}</div>
}

Параметры:

redirect(path: string, type?: 'replace' | 'push')

Важно:

  • Бросает ошибку NEXT_REDIRECT - вызывайте вне блока try/catch
  • В Server Actions использует 303 redirect
  • В остальных случаях использует 307 redirect
  • Поддерживает абсолютные URL для внешних ссылок
  • В Client Components работает только при рендеринге, не в обработчиках событий

Альтернатива: permanentRedirect() для 308 redirect


notFound()

Назначение: Отображение страницы 404 (not-found).

Импорт:

import { notFound } from 'next/navigation'

Пример:

import { notFound } from 'next/navigation'

export default async function Page({ params }) {
  const { id } = await params
  const user = await fetchUser(id)
  
  if (!user) {
    notFound()
  }
  
  return <div>{user.name}</div>
}

Важно:

  • Бросает ошибку NEXT_HTTP_ERROR_FALLBACK;404
  • Автоматически добавляет <meta name="robots" content="noindex" />
  • Рендерит файл not-found.tsx в текущем сегменте маршрута
  • Не требует return благодаря TypeScript типу never

useRouter()

Назначение: Программная навигация в Client Components.

Импорт:

import { useRouter } from 'next/navigation'

Пример:

'use client'

import { useRouter } from 'next/navigation'

export default function Page() {
  const router = useRouter()
  
  return (
    <button onClick={() => router.push('/dashboard')}>
      Go to Dashboard
    </button>
  )
}

Доступные методы:

  • push(href, options) - навигация с добавлением в историю
  • replace(href, options) - навигация без добавления в историю
  • refresh() - обновление текущей страницы
  • prefetch(href) - предварительная загрузка маршрута
  • back() - назад
  • forward() - вперед

Важно:

  • Работает только в Client Components
  • Для навигации предпочитайте <Link> компонент
  • Опция scroll: false отключает прокрутку вверх

next/cache

revalidatePath()

Назначение: Инвалидация кэшированных данных для конкретного пути.

Импорт:

import { revalidatePath } from 'next/cache'

Примеры:

Инвалидация конкретной страницы:

import { revalidatePath } from 'next/cache'

revalidatePath('/blog/post-1')

Инвалидация всех страниц с динамическим сегментом:

import { revalidatePath } from 'next/cache'

revalidatePath('/blog/[slug]', 'page')

Инвалидация layout:

import { revalidatePath } from 'next/cache'

revalidatePath('/blog/[slug]', 'layout')

Использование в Server Action:

'use server'

import { revalidatePath } from 'next/cache'

export async function updatePost() {
  await savePost()
  revalidatePath('/blog')
}

Параметры:

revalidatePath(path: string, type?: 'page' | 'layout')

Важно:

  • Работает только в Server Functions и Route Handlers
  • При использовании с rewrites указывайте destination path
  • Инвалидация происходит при следующем посещении пути

revalidateTag()

Назначение: Инвалидация кэшированных данных по тегам.

Импорт:

import { revalidateTag } from 'next/cache'

Пример:

Добавление тега к fetch:

const posts = await fetch('https://api.example.com/posts', {
  next: { tags: ['posts'] }
})

Инвалидация по тегу:

'use server'

import { revalidateTag } from 'next/cache'

export async function addPost() {
  await savePost()
  revalidateTag('posts', 'max')
}

Параметры:

revalidateTag(tag: string, profile?: string | { expire?: number })

Важно:

  • Рекомендуется использовать profile: 'max' для stale-while-revalidate
  • Инвалидирует данные во всех страницах, использующих этот тег
  • Работает только в Server Functions и Route Handlers

Клиентские хуки

usePathname()

Назначение: Получение текущего пути URL в Client Components.

'use client'

import { usePathname } from 'next/navigation'

export default function Page() {
  const pathname = usePathname()
  return <div>Current path: {pathname}</div>
}

useSearchParams()

Назначение: Получение параметров поиска (query string) в Client Components.

'use client'

import { useSearchParams } from 'next/navigation'

export default function Page() {
  const searchParams = useSearchParams()
  const query = searchParams.get('q')
  
  return <div>Search: {query}</div>
}

Важно: Вызывает клиентский рендеринг до ближайшего Suspense boundary


Сводная таблица

APIИмпортГде работаетНазначение
headers()next/headersServer ComponentsЧтение HTTP заголовков
cookies()next/headersServer Components, Server FunctionsРабота с cookies
draftMode()next/headersServer Components, Route HandlersУправление режимом черновика
redirect()next/navigationServer/Client Components, Server ActionsПеренаправление
notFound()next/navigationServer/Client ComponentsОтображение 404
useRouter()next/navigationClient ComponentsПрограммная навигация
usePathname()next/navigationClient ComponentsТекущий путь
useSearchParams()next/navigationClient ComponentsПараметры поиска
revalidatePath()next/cacheServer Functions, Route HandlersИнвалидация кэша по пути
revalidateTag()next/cacheServer Functions, Route HandlersИнвалидация кэша по тегам

Лучшие практики

1. Работа с cookies

// ✅ Правильно: чтение в Server Component
export default async function Page() {
  const cookieStore = await cookies()
  const theme = cookieStore.get('theme')
  return <div>{theme?.value}</div>
}

// ✅ Правильно: запись в Server Action
'use server'
export async function updateTheme(theme: string) {
  const cookieStore = await cookies()
  cookieStore.set('theme', theme)
}

// ❌ Неправильно: попытка записи в Server Component
export default async function Page() {
  const cookieStore = await cookies()
  cookieStore.set('theme', 'dark') // Ошибка!
}

2. Обработка ошибок с redirect

// ✅ Правильно: redirect вне try/catch
export default async function Page({ params }) {
  const { id } = await params
  
  try {
    const data = await fetchData(id)
    if (!data) redirect('/not-found')
    return <div>{data.title}</div>
  } catch (error) {
    // Обработка других ошибок
    console.error(error)
    return <div>Error occurred</div>
  }
}

// ❌ Неправильно: redirect внутри try/catch
export default async function Page({ params }) {
  const { id } = await params
  
  try {
    const data = await fetchData(id)
    if (!data) redirect('/not-found') // Будет пойман!
  } catch (error) {
    // redirect бросает ошибку, которая будет поймана здесь
  }
}

3. Комбинирование revalidatePath и revalidateTag

'use server'

import { revalidatePath, revalidateTag } from 'next/cache'

export async function updatePost(id: string) {
  await savePost(id)
  
  // Инвалидация конкретной страницы
  revalidatePath(`/blog/${id}`)
  
  // Инвалидация всех страниц с постами
  revalidateTag('posts', 'max')
}

4. Использование headers для авторизации

import { headers } from 'next/headers'

export default async function Page() {
  const headersList = await headers()
  const authorization = headersList.get('authorization')
  
  const res = await fetch('https://api.example.com/user', {
    headers: { authorization }
  })
  
  const user = await res.json()
  return <div>{user.name}</div>
}

Динамический рендеринг

Использование следующих API автоматически делает маршрут динамическим:

  • headers()
  • cookies()
  • draftMode()
  • searchParams в page components

Это означает, что страница будет рендериться при каждом запросе, а не кэшироваться статически.


Ключевые тезисы для интервью

  • headers() из next/headers возвращает read-only объект HTTP-заголовков и делает маршрут динамическим.
  • cookies() позволяет читать в Server Components, а записывать — только в Server Functions и Route Handlers.
  • draftMode() управляет режимом черновика через cookie __prerender_bypass и обходит кэширование.
  • redirect() бросает ошибку NEXT_REDIRECT, поэтому его нельзя вызывать внутри try/catch.
  • notFound() прерывает рендеринг и автоматически отображает not-found.tsx с noindex.
  • useRouter() работает только в Client Components и предоставляет методы push, replace, refresh, prefetch, back, forward.
  • useSearchParams() и usePathname() возвращают клиентские данные URL и требуют ближайшего Suspense boundary.
  • revalidatePath() инвалидирует кэш по конкретному пути или layout, а revalidateTag() — по тегам fetch.
  • headers(), cookies(), draftMode() и searchParams в page-компонентах автоматически переключают маршрут в динамический рендеринг.
  • Для cookies важно разделять чтение в Server Components и запись в Server Actions, иначе возникнет ошибка.

Заключение

Next.js App Router даёт чёткое разделение между серверными и клиентскими API: headers(), cookies() и draftMode() работают на сервере, а useRouter(), usePathname() и useSearchParams() — только в клиентских компонентах. Понимание этих границ помогает избежать ошибок при записи cookies и вызове redirect(). Правильное использование revalidatePath() и revalidateTag() позволяет эффективно управлять кэшем без лишнего рендеринга. Попробуйте перенести запись cookies из Server Components в Server Actions и проверьте, какие маршруты становятся динамическими.

Полезные ссылки