Получение, кэширование и ревалидация данных

Получение данных — это ключевая часть любого приложения. На этой странице объясняется, как можно получать, кэшировать и ревалидировать данные в React и Next.js.

Существует четыре способа получения данных:

  1. На сервере с помощью fetch
  2. На сервере с помощью сторонних библиотек
  3. На клиенте через Route Handler
  4. На клиенте с помощью сторонних библиотек.

Получение данных на сервере с fetch

Next.js расширяет нативный Web API fetch, позволяя настраивать поведение кэширования и ревалидации для каждого запроса на сервере. React расширяет fetch, автоматически мемоизируя запросы при рендеринге дерева React-компонентов.

Вы можете использовать fetch с async/await в Server Components, Route Handlers и Server Actions.

Например:

async function getData() {
  const res = await fetch('https://api.example.com/...')
  // Возвращаемое значение *не* сериализуется
  // Можно возвращать Date, Map, Set и т.д.

  if (!res.ok) {
    // Активирует ближайший Error Boundary из `error.js`
    throw new Error('Не удалось получить данные')
  }

  return res.json()
}

export default async function Page() {
  const data = await getData()

  return <main></main>
}
async function getData() {
  const res = await fetch('https://api.example.com/...')
  // Возвращаемое значение *не* сериализуется
  // Можно возвращать Date, Map, Set и т.д.

  if (!res.ok) {
    // Активирует ближайший Error Boundary из `error.js`
    throw new Error('Не удалось получить данные')
  }

  return res.json()
}

export default async function Page() {
  const data = await getData()

  return <main></main>
}

Полезно знать:

  • Next.js предоставляет полезные функции, которые могут понадобиться при получении данных в Server Components, такие как cookies и headers. Они приводят к динамическому рендерингу маршрута, так как зависят от информации о времени запроса.
  • В Route Handlers запросы fetch не мемоизируются, так как Route Handlers не являются частью дерева React-компонентов.
  • В Server Actions запросы fetch не кэшируются (по умолчанию cache: no-store).
  • Для использования async/await в Server Component с TypeScript требуется TypeScript 5.1.3 или выше и @types/react 18.2.8 или выше.

Кэширование данных

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

По умолчанию Next.js автоматически кэширует возвращаемые значения fetch в Data Cache на сервере. Это означает, что данные могут быть получены во время сборки или запроса, закэшированы и повторно использованы при каждом запросе данных.

// 'force-cache' используется по умолчанию и может быть опущен
fetch('https://...', { cache: 'force-cache' })

Однако есть исключения — запросы fetch не кэшируются, когда:

  • Используются внутри Server Action.
  • Используются внутри Route Handler с методом POST.

Что такое Data Cache?

Data Cache — это постоянный HTTP-кэш. В зависимости от платформы кэш может масштабироваться автоматически и распределяться между несколькими регионами.

Подробнее о Data Cache.

Ревалидация данных

Ревалидация — это процесс очистки Data Cache и повторного получения актуальных данных. Это полезно, когда ваши данные изменяются и вы хотите убедиться, что отображается самая свежая информация.

Закэшированные данные можно ревалидировать двумя способами:

  • Ревалидация по времени: Автоматическая ревалидация данных через определенный промежуток времени. Полезно для данных, которые изменяются редко, и их актуальность не критична.
  • Ревалидация по запросу: Ручная ревалидация данных на основе события (например, отправки формы). Ревалидация по запросу может использовать теги или пути для одновременной ревалидации групп данных. Полезно, когда нужно как можно быстрее показать актуальные данные (например, при обновлении контента из headless CMS).

Ревалидация по времени

Для ревалидации данных через определенные интервалы можно использовать опцию next.revalidate в fetch, чтобы установить время жизни ресурса в кэше (в секундах).

fetch('https://...', { next: { revalidate: 3600 } })

Альтернативно, для ревалидации всех запросов fetch в сегменте маршрута можно использовать Segment Config Options.

layout.js | page.js
export const revalidate = 3600 // ревалидировать не чаще чем раз в час

Если в статически рендерящемся маршруте есть несколько запросов fetch с разной частотой ревалидации, для всех запросов будет использовано наименьшее время. Для динамически рендерящихся маршрутов каждый запрос fetch будет ревалидироваться независимо.

Подробнее о ревалидации по времени.

Ревалидация по запросу

Данные можно ревалидировать по запросу по пути (revalidatePath) или по тегу кэша (revalidateTag) внутри Server Action или Route Handler.

Next.js имеет систему тегирования кэша для инвалидации запросов fetch между маршрутами.

  1. При использовании fetch можно пометить записи кэша одним или несколькими тегами.
  2. Затем можно вызвать revalidateTag, чтобы ревалидировать все записи, связанные с этим тегом.

Например, следующий запрос fetch добавляет тег кэша collection:

export default async function Page() {
  const res = await fetch('https://...', { next: { tags: ['collection'] } })
  const data = await res.json()
  // ...
}
export default async function Page() {
  const res = await fetch('https://...', { next: { tags: ['collection'] } })
  const data = await res.json()
  // ...
}

Затем можно ревалидировать этот вызов fetch с тегом collection, вызвав revalidateTag в Server Action:

'use server'

import { revalidateTag } from 'next/cache'

export default async function action() {
  revalidateTag('collection')
}
'use server'

import { revalidateTag } from 'next/cache'

export default async function action() {
  revalidateTag('collection')
}

Подробнее о ревалидации по запросу.

Обработка ошибок и ревалидация

Если при попытке ревалидации данных возникает ошибка, из кэша будут продолжать обслуживаться последние успешно сгенерированные данные. При следующем запросе Next.js повторно попытается ревалидировать данные.

Отказ от кэширования данных

Запросы fetch не кэшируются, если:

  • Добавлен параметр cache: 'no-store' в запрос fetch.
  • Добавлена опция revalidate: 0 в отдельный запрос fetch.
  • Запрос fetch находится внутри Route Handler с методом POST.
  • Запрос fetch выполняется после использования headers или cookies.
  • Используется опция сегмента маршрута const dynamic = 'force-dynamic'.
  • Опция сегмента маршрута fetchCache настроена на пропуск кэширования по умолчанию.
  • Запрос fetch использует заголовки Authorization или Cookie, и выше в дереве компонентов есть некэшируемый запрос.

Отдельные запросы fetch

Чтобы отказаться от кэширования для отдельных запросов fetch, можно установить параметр cache в 'no-store'. Это позволит получать данные динамически при каждом запросе.

layout.js | page.js
fetch('https://...', { cache: 'no-store' })

Все доступные параметры cache можно посмотреть в справочнике API fetch.

Несколько запросов fetch

Если в сегменте маршрута (например, в Layout или Page) есть несколько запросов fetch, можно настроить поведение кэширования для всех запросов данных в сегменте с помощью Segment Config Options.

Однако рекомендуется настраивать поведение кэширования для каждого запроса fetch индивидуально. Это обеспечивает более детальный контроль над кэшированием.

Получение данных на сервере со сторонними библиотеками

Если вы используете стороннюю библиотеку, которая не поддерживает или не предоставляет доступ к fetch (например, клиент базы данных, CMS или ORM), можно настроить поведение кэширования и ревалидации таких запросов с помощью Route Segment Config Option и функции React cache.

Будет ли кэшироваться результат запроса, зависит от того, статически или динамически рендерится сегмент маршрута. Если сегмент статический (по умолчанию), результат запроса будет кэшироваться и ревалидироваться как часть сегмента маршрута. Если сегмент динамический, результат запроса не будет кэшироваться и будет перезапрашиваться при каждом рендеринге сегмента.

Также можно использовать экспериментальный API unstable_cache.

Пример

В следующем примере:

  • Функция React cache используется для мемоизации запросов данных.
  • Опция revalidate установлена в 3600 в сегментах Layout и Page, что означает, что данные будут кэшироваться и ревалидироваться не чаще чем раз в час.
import { cache } from 'react'

export const getItem = cache(async (id: string) => {
  const item = await db.item.findUnique({ id })
  return item
})
import { cache } from 'react'

export const getItem = cache(async (id) => {
  const item = await db.item.findUnique({ id })
  return item
})

Хотя функция getItem вызывается дважды, будет выполнен только один запрос к базе данных.

import { getItem } from '@/utils/get-item'

export const revalidate = 3600 // ревалидировать данные не чаще чем раз в час

export default async function Layout({
  params: { id },
}: {
  params: { id: string }
}) {
  const item = await getItem(id)
  // ...
}
import { getItem } from '@/utils/get-item'

export const revalidate = 3600 // ревалидировать данные не чаще чем раз в час

export default async function Layout({ params: { id } }) {
  const item = await getItem(id)
  // ...
}
import { getItem } from '@/utils/get-item'

export const revalidate = 3600 // ревалидировать данные не чаще чем раз в час

export default async function Page({
  params: { id },
}: {
  params: { id: string }
}) {
  const item = await getItem(id)
  // ...
}
import { getItem } from '@/utils/get-item'

export const revalidate = 3600 // ревалидировать данные не чаще чем раз в час

export default async function Page({ params: { id } }) {
  const item = await getItem(id)
  // ...
}

Получение данных на клиенте с Route Handlers

Если нужно получить данные в клиентском компоненте, можно вызвать Route Handler из клиента. Route Handlers выполняются на сервере и возвращают данные клиенту. Это полезно, когда не хочется раскрывать конфиденциальную информацию клиенту, например, API-токены.

Примеры можно найти в документации по Route Handlers.

Server Components и Route Handlers

Поскольку Server Components рендерятся на сервере, вам не нужно вызывать Route Handler из Server Component для получения данных. Вместо этого можно получать данные напрямую внутри Server Component.

Получение данных на клиенте со сторонними библиотеками

Также можно получать данные на клиенте с помощью сторонних библиотек, таких как SWR или TanStack Query. Эти библиотеки предоставляют собственные API для мемоизации запросов, кэширования, ревалидации и мутации данных.

Будущие API:

use — это функция React, которая принимает и обрабатывает промис, возвращаемый функцией. Обертывание fetch в use в настоящее время не рекомендуется в клиентских компонентах и может вызывать множественные ререндеры. Подробнее о use в документации React.