Загрузка данных и кэширование в Next.js: подробный разбор
Продвинутое4 минРедакция Innovialabs
Коротко
В Next.js вы сами решаете, что кэшировать и как долго: данные можно обновлять по таймеру или по тегу после изменения, а динамические части стримить через Suspense. Устаревшие данные почти всегда объясняются тем, что страница или запрос неожиданно оказались закэшированы.
Коротко: как Next.js получает и хранит данные
В App Router данные загружаются прямо в серверных компонентах: компонент может быть async, а внутри можно вызвать fetch, ORM или любой SDK. Next.js добавляет к этому несколько слоёв кэша, и именно они определяют, увидит ли пользователь свежие данные.
Важно: поведение по умолчанию менялось между мажорными версиями Next.js. В одних версиях fetch кэшировался автоматически, в других — нет, а в новых появилась директива "use cache". Поэтому перед работой сверяйтесь с документацией именно вашей версии, а в коде задавайте поведение явно.
Три вопроса, которые стоит задать каждому запросу
- Можно ли эти данные переиспользовать между пользователями? Каталог товаров — да. Корзина конкретного человека — нет.
- Как долго они могут быть устаревшими? Курс валют — минуты, статья блога — часы или до момента редактирования.
- Что должно сбрасывать кэш? Таймер или конкретное событие: публикация, оплата, изменение цены.
Ответы на эти вопросы и определяют стратегию.
Ревалидация по времени
Подходит, когда данные меняются регулярно, но точный момент изменения вам неизвестен.
export default async function Prices() {
const res = await fetch("https://api.example.com/prices", {
next: { revalidate: 300 },
});
const prices = await res.json();
return <PriceTable data={prices} />;
}
Логика stale-while-revalidate: в течение 300 секунд отдаётся кэш, после этого первый запрос всё ещё получает старую версию, а в фоне готовится новая. Значит, один пользователь может увидеть данные чуть старше заданного интервала — это нормально.
Ревалидация по тегу
Подходит, когда вы точно знаете, когда данные изменились: админ сохранил статью, пришёл webhook из CRM.
// Загрузка
const res = await fetch("https://api.example.com/posts", {
next: { tags: ["posts"] },
});
// Server Action после сохранения
"use server";
import { revalidateTag } from "next/cache";
export async function savePost(data: FormData) {
await db.post.update(/* ... */);
revalidateTag("posts", "max");
}
В свежих версиях revalidateTag принимает второй аргумент — профиль кэша, в старых — только тег. Есть и revalidatePath("/blog"), который сбрасывает кэш конкретного маршрута. Теги удобнее: один тег можно повесить на десятки запросов на разных страницах.
Статический и динамический рендеринг
- Статический — страница собирается при билде или при ревалидации и отдаётся из кэша. Быстро и дёшево.
- Динамический — страница рендерится на каждый запрос. Нужен, когда ответ зависит от пользователя.
Страница становится динамической, если вы используете cookies(), headers(), searchParams или запрос без кэша (cache: "no-store"). Частая ловушка: одно обращение к cookies() в layout делает динамическим весь вложенный раздел.
| Ситуация | Что выбрать |
|---|---|
| Лендинг, документация | Статика |
| Блог с редактированием из CMS | Статика + теги |
| Каталог с ценами из API | Статика + revalidate по времени |
| Личный кабинет, корзина | Динамика |
Стриминг с Suspense
Если на странице есть медленный блок, не заставляйте всю страницу ждать его. Оберните его в Suspense — остальная часть придёт сразу, а медленная догрузится потоком.
import { Suspense } from "react";
export default function Dashboard() {
return (
<>
<Header />
<Suspense fallback={<Skeleton />}>
<SlowReport />
</Suspense>
</>
);
}
Файл loading.tsx в папке маршрута делает то же самое для всей страницы. Совет: запускайте независимые запросы параллельно через Promise.all, а не цепочкой await, иначе получится «водопад» задержек.
Как отлаживать устаревшие данные
- Проверьте, статическая ли страница. Вывод
next buildпоказывает тип каждого маршрута. - Проверяйте в production-режиме. В
next devкэш ведёт себя иначе, поэтому баги часто видны только послеnext build && next start. - Убедитесь, что тег совпадает при загрузке и при ревалидации — опечатка в строке ломает всё молча.
- Не забывайте про клиентский роутер. После мутации вызывайте ревалидацию на сервере или
router.refresh(), иначе клиент может показать прежний результат. - Учитывайте CDN. Если перед приложением стоит CDN со своими правилами, он может держать старый ответ независимо от Next.js.
- Логируйте. Временный
console.logв серверном компоненте сразу показывает, выполнился ли он заново.
Частые ошибки
- Кэширование персональных данных в общем кэше — риск показать чужие данные.
no-storeвезде «на всякий случай» — сайт становится медленным и дорогим.- Последовательные
awaitвместо параллельной загрузки. - Надежда на поведение по умолчанию вместо явных настроек.