Next.js 16.4 (вийшов 6 жовтня) офіційно рекомендує Cache Components як найкращу модель для кожного застосунку: нові проєкти через create-next-app отримують їх увімкненими, а в Next.js 17 це стане default для всіх. Якщо коротко, фреймворк остаточно йде від моделі «вгадай, кешується цей fetch чи ні» до моделі, де кеш – явна, декларативна властивість частини дерева компонентів. Це хороший напрям. Але слово «default» не означає «включи й забудь» – розбираємось, що реально змінюється і як мігрувати без сюрпризів.

Що саме ввімкнули в 16.4

Найважливіша зміна 16.4 – не чергові відсотки швидкості Turbopack, а офіційна позиція команди. Дослівно з анонсу: «Starting with Next.js 16.4, we're excited to recommend Cache Components as the best choice for every Next.js app». До цього релізу рекомендація не була універсальною: у деяких сценаріях нова модель не давала тих самих гарантій вартості й продуктивності, що стара. 16.4 закриває ці прогалини, а Next.js 17 зробить модель дефолтною вже для всіх.

Сьогодні Cache Components вмикаються двома прапорцями в конфігу:

import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  cacheComponents: true,
  partialPrefetching: true,
};

export default nextConfig;

Зверни увагу: Partial Prefetching тепер офіційно частина моделі, а не окрема опція. Як він працює, ми детально розбирали в статті про Instant Navigations.

Ментальна модель: кеш як властивість компонента

Команда Next.js пропонує думати про 'use cache' як про компонентну версію HTTP-заголовка Cache-Control:

import { cacheLife } from 'next/cache';

export default async function DashboardPage() {
  const currentUser = await getCurrentUser();

  return (
    <div>
      <p>Вітаю, {currentUser.name}</p>

      <Suspense fallback={<Loading />}>
        <Projects userId={currentUser.id} />
      </Suspense>
    </div>
  );
}

async function Projects({ userId }: { userId: string }) {
  'use cache';
  cacheLife('hours');

  return db.query.projects.findMany({
    where: eq(projectsTable.userId, userId),
  });
}

Одна сторінка поєднує персоналізовану request-time частину (привітання з іменем) і кешований UI (список проєктів живе годинами). Це ближче до того, як продуктовий інженер реально думає: не «ця route статична чи динамічна?», а «яка конкретно частина UI потребує свіжих даних?».

Семантику тегів, revalidateTag/updateTag і read-your-own-writes ми докладно розбирали в ментальній моделі кешу Next.js – вона лишається чинною, 16.4 нічого в ній не ламає, лише додає гарантій зверху.

ensureStatic: build-time guardrail замість тихої деградації

Найнедооціненіша фіча релізу. Стара болячка App Router: один випадково доданий динамічний компонент тихо міняв характеристики цілої сторінки – і твій «статичний» блог раптом рендериться на кожен request, а рахунок за compute росте.

Тепер це можна заборонити на рівні build:

export const ensureStatic = 'navigation';

export default function Page() {
  return (
    <>
      <UserAvatar /> {/* 🔴 build впаде: динаміка у статичній route */}
      <Content />
    </>
  );
}

Значення три, за рівнем суворості: 'navigation' (навігації на route ніколи не рендеряться на request), 'prefetch' (префетчі тягнуть лише статичне) і 'shell' (статичним гарантовано лише первинний shell). Можна ставити на layout – і тоді гарантія поширюється на всі сторінки під ним, аж до «увесь сайт статичний, окремі розділи – виняток».

Для блогу, маркетингових сторінок, документації чи ecommerce-категорій це саме той архітектурний контракт, який раніше жив тільки в головах: тепер його порушення ловить CI, а не рахунок наприкінці місяця.

Prefetch – чудова річ, поки список із 50 посилань не вирішив завчасно завантажити пів бази даних. Класичний приклад з анонсу – inbox: хочеться префетчити перший екран повідомлення, але не тягнути entire thread для кожного видимого лінка.

import { navigation } from 'next/cache';

async function Thread({ id }: { id: string }) {
  await navigation(); // виключає компонент із префетчу
  const thread = await getThread(id);
  // рендериться лише після реальної навігації
}

await navigation() каже: «цю частину не вантаж на prefetch, дочекайся справжнього переходу». Симетричний await prefetch() робить навпаки: виключає вміст із shell, відкладаючи його до явного префетчу.

Маленькі API з великою ідеєю: спекулятивна робота має бути явним бюджетом. Frontend performance давно не лише про bundle size – у server-centric фреймворку prefetch може покращити UX і одночасно роздути backend cost. Тепер цей компроміс контрольований.

Як зламати собі кеш у перший день

  1. Масове додавання 'use cache' у день міграції. Спочатку розклади сторінки на три кошики: глобальні рідкозмінні дані, користувацькі повторно використовувані, request-specific/security-sensitive. Кешові межі малюються після цього, не до.
  2. Кешування того, що вимагає read-your-own-writes. Користувач зберіг налаштування і бачить старі – це баг довіри, а не performance.
  3. Ігнорування ensureStatic на «очевидно статичних» сторінках. Саме там він найдешевший: один рядок сьогодні проти тихої деградації через пів року.
  4. Сліпе оновлення у день релізу. 16.4 стабільний, але питання в твоєму коді: у code review тепер питай не «чому це dynamic?», а «який freshness contract у цієї частини UI?».

Тест-драйв на власному route

Візьми свій найбільш «гібридний» route (трохи персоналізації + багато спільного контенту) і випиши для кожної секції UI її freshness contract: скільки даним можна жити і хто їх інвалідує. Потім перевір, чи відповідає цьому фактична поведінка. Б'юсь об заклад, знайдеш щонайменше одну секцію, що рендериться на кожен request без жодної причини.

Найцікавіше в 16.4 – навіть не API, а напрям: App Router стає менш «framework decides» і більш «developer declares intent». Для тих, хто готується до співбесід із Next.js, базовий огляд моделі – у гайді для React-розробника.