Skip to content
griban.dev
← назад_к_блогу
nextjs

Производительность Next.js App Router: оптимизация Next.js 15 и 16

Ruslan Griban8 мин чтения
поделиться:

Выход Next.js 15 и 16 знаменует собой важный сдвиг в подходе к производительности веб-приложений. Мы ушли от эпохи «магического» неявного кэширования и вступили в эру явного, гранулярного контроля. С внедрением стабильного React Compiler, Partial Prerendering (PPR) и революционной директивы use cache, App Router превратился в высокопроизводительный движок, способный обеспечить загрузку менее чем за секунду даже для сложных приложений с большим объемом данных.

Оптимизация приложения Next.js в 2025 году — это уже не просто минификация JavaScript; это хирургически точная оркестрация движения данных между сервером и клиентом. В этом руководстве рассматриваются передовые методы, необходимые для освоения производительности в современной экосистеме Next.js.

Революция кэширования: от неявного к явному

В ранних версиях App Router кэширование часто было «неявным». Один вызов динамической функции, такой как cookies() или headers(), мог неожиданно исключить весь маршрут из кэширования. Next.js 16 фундаментально изменил это с помощью модели «Cache Components».

Директива use cache

Директива use cache — это самое значительное достижение в Next.js 16. Она позволяет разработчикам явно включать кэширование на уровне функции или файла. Это кладет конец непредсказуемости предыдущей модели кэширования.

  • Кэширование на уровне функций: теперь вы можете обернуть определенную логику (например, запрос к базе данных или вызов внешнего API) в асинхронную функцию и пометить ее строкой 'use cache';. Next.js будет кэшировать возвращаемое значение на основе сериализованных аргументов.
  • Кэширование на уровне файлов: размещение 'use cache'; в верхней части файла кэширует каждую экспортируемую функцию внутри этого файла.
// services/products.ts
import { unstable_cacheLife as cacheLife } from 'next/cache';
 
export async function getProductDetails(productId: string) {
  'use cache'; // Явное включение кэширования для этой функции
  cacheLife('minutes'); // Определение профиля истечения срока действия
 
  const res = await fetch(`https://api.acme.com/products/${productId}`);
  if (!res.ok) throw new Error('Failed to fetch product');
  
  return res.json();
}

API для гранулярного управления кэшем

Для поддержки этой явной модели Next.js представил несколько новых API для управления кэшем:

  1. cacheLife: заменяет старые константы revalidate. Он принимает профили, такие как 'minutes', 'hours' или 'days', что упрощает управление TTL (Time To Live) в различных средах.
  2. cacheTag и revalidateTag: они остаются золотым стандартом для аннулирования кэша по требованию. Пометив закэшированный сегмент тегом, вы можете мгновенно очистить его при изменении данных (например, после вебхука из CMS).

Техническая диаграмма, показывающая архитектуру кэша Next.js 16, иллюстрирующая, как директива 'use cache' взаимодействует с Data Cache и Client-side Router Cache для эффективного обслуживания запросов.

Освоение паттернов рендеринга: PPR и Streaming

Цель оптимизации производительности — уменьшить Time to First Byte (TTFB) и Interaction to Next Paint (INP). Next.js 16 достигает этого за счет стабилизации Partial Prerendering (PPR).

Стабильный Partial Prerendering (PPR)

PPR позволяет комбинировать статический и динамический рендеринг на одной странице без лишних сложностей в конфигурации. Когда пользователь запрашивает страницу, Next.js немедленно отдает статическую HTML-оболочку (содержащую ваш макет, навигацию и статический контент). «Динамические дыры» (dynamic holes) — части страницы, требующие специфических для пользователя данных — оборачиваются в границы <Suspense> и подгружаются потоком (streaming) по мере готовности.

Чтобы включить PPR, настройте его в next.config.js:

// next.config.js
const nextConfig = {
  experimental: {
    ppr: 'incremental', // Постепенное внедрение PPR для ваших маршрутов
  },
};
module.exports = nextConfig;

Роль React 19 и React Compiler

Next.js 16 использует стабильный React Compiler. Раньше разработчики тратили много времени на ручную оптимизацию компонентов с помощью useMemo, useCallback и React.memo, чтобы предотвратить ненужные рендеринги. React Compiler автоматизирует этот процесс, анализируя ваш код и применяя мемоизацию там, где она приносит наибольшую пользу.

Это приводит к:

  • Снижению нагрузки на основной поток (Main Thread): компоненты перерисовываются только тогда, когда меняются их фактические зависимости данных.
  • Улучшению INP: за счет сокращения объема работы, которую React выполняет во время обновлений, браузер быстрее реагирует на ввод пользователя.

Стратегии высокопроизводительного получения данных

Получение данных часто является основным узким местом в веб-приложениях. В App Router мы должны уйти от последовательных «водопадов» (waterfalls) к параллельному выполнению и стримингу.

Устранение водопадов запросов

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

Антипаттерн (медленно):

const user = await getUser(); // Занимает 500мс
const posts = await getPosts(user.id); // Занимает 500мс (Итого: 1000мс)

Оптимизированный паттерн (быстро):

// Инициализация обоих запросов параллельно
const userPromise = getUser();
const postsPromise = getPosts();
 
// Ожидание завершения обоих промисов
const [user, posts] = await Promise.all([userPromise, postsPromise]);

Использование Server Components для оптимизации «листьев»

Чтобы минимизировать объем JavaScript, отправляемый клиенту, следует держать ваши Client Components на «листьях» (окончаниях) дерева компонентов. Если вы пометите высокоуровневый макет директивой "use client", каждый компонент, импортируемый в этот макет, станет частью клиентского бандла.

Вместо этого передавайте Server Components как children или пропсы в Client Components, чтобы сохранить преимущества подхода Server-First.

// Плохо: вся страница является Client Component
"use client"
export default function Dashboard({ data }) {
  return <Sidebar>{/* сложная логика */}</Sidebar>;
}
 
// Хорошо: только интерактивный переключатель является Client Component
export default function DashboardPage() {
  return (
    <Sidebar>
      <Suspense fallback={<Skeleton />}>
        <DataList /> {/* Server Component */}
      </Suspense>
      <InteractiveToggle /> {/* Client Component */}
    </Sidebar>
  );
}

Архитектурная визуализация дерева компонентов React, где директивы 'use client' размещены в «листьях» (самых нижних компонентах), что подчеркивает сокращение объема JavaScript, отправляемого в браузер.

Оптимизация ресурсов и переход на Turbopack

Ресурсы — изображения, шрифты и скрипты — часто составляют большую часть веса страницы. Next.js предоставляет встроенные примитивы для их автоматической обработки.

Оптимизация изображений с помощью next/image

Компонент next/image — это больше, чем просто тег <img>. В 2025 году крайне важно использовать атрибут priority для элементов Largest Contentful Paint (LCP) и атрибут sizes, чтобы браузер не загружал изображения, которые слишком велики для текущего окна просмотра.

<Image
  src="/hero.jpg"
  alt="Hero Image"
  width={1200}
  height={600}
  priority // Важно для LCP
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>

Оптимизация шрифтов и отсутствие сдвигов макета

Использование next/font позволяет хостить шрифты локально и автоматически управляет свойством font-display. Это устраняет сдвиги макета (CLS), вызванные загрузкой шрифтов после первоначального рендеринга. Используя CSS-переменные с next/font, вы гарантируете, что ваша типографика будет готова в момент парсинга HTML.

Turbopack: новый стандарт

Поскольку Turbopack теперь является сборщиком по умолчанию в Next.js 16, время сборки и скорость Fast Refresh значительно улучшились. Turbopack использует движок инкрементальных вычислений, написанный на Rust, который пересобирает только тот код, который изменился. Для крупных приложений это может сократить время сборки в продакшн до 80%.

Мониторинг и проверка в реальных условиях

Оптимизация производительности — это не разовая задача. Вы должны проверять свои улучшения с помощью мониторинга реальных пользователей (RUM).

  1. Vercel Speed Insights: этот инструмент предоставляет живой дашборд Core Web Vitals вашего приложения (LCP, INP, CLS) на основе данных реальных пользователей.
  2. Sentry и OpenTelemetry: для более глубокого анализа, особенно в Server Components и API-маршрутах, используйте трассировку на базе OpenTelemetry. Это позволит вам точно увидеть, какой запрос к базе данных или внешнему API замедляет ваш Server Side Rendering (SSR).
  3. Zod для безопасности во время выполнения: хотя это не прямой инструмент для «скорости», использование Zod для валидации ответов API гарантирует, что ваше приложение не упадет и не зависнет из-за неожиданных структур данных, что может привести к плохой воспринимаемой производительности.

Скриншот дашборда мониторинга производительности, показывающий показатели Core Web Vitals (LCP, FID, CLS) и график водопада сетевых запросов в приложении Next.js.

Часто задаваемые вопросы

Как оптимизировать производительность в Next.js App Router?

Оптимизация в App Router сосредоточена на использовании Server Components по умолчанию для сокращения клиентского JavaScript и внедрении Partial Prerendering (PPR) для быстрой начальной загрузки. Кроме того, используйте директиву use cache для гранулярного кэширования данных и убедитесь, что вы используете next/image и next/font для оптимизации ресурсов.

Является ли Next.js App Router быстрее, чем Pages Router?

App Router обычно быстрее, так как он поддерживает React Server Components (RSC), которые значительно уменьшают объем JavaScript, отправляемого в браузер. Он также поддерживает такие продвинутые функции, как Streaming и Partial Prerendering, которые недоступны в Pages Router, что ведет к лучшим показателям Core Web Vitals.

Как уменьшить размер JavaScript-бандла в Next.js?

Чтобы уменьшить размер бандла, перенесите интерактивность в «листья» дерева компонентов и используйте динамические импорты (next/dynamic) для тяжелых сторонних библиотек. Избегайте размещения директивы "use client" на верхнем уровне ваших макетов, так как это принудительно включает все дочерние компоненты в клиентский бандл.

Как работает кэширование в Next.js App Router?

В Next.js 16 кэширование является явным благодаря директиве use cache, позволяющей кэшировать функции или файлы с определенными профилями TTL с помощью cacheLife. Система также использует многоуровневое кэширование, включая Request Memoization, Data Cache и Full Route Cache, для минимизации избыточных вычислений.

Каковы лучшие практики получения данных в Next.js?

Всегда получайте данные на сервере с помощью Server Components, чтобы держать логику ближе к источнику данных и снизить нагрузку на клиент. Используйте параллельное получение данных с Promise.all, чтобы избежать водопадов, и оборачивайте медленные запросы в границы <Suspense> для включения стриминга и улучшения пользовательского опыта.

Заключение

Оптимизация производительности в Next.js App Router — это путь к явному контролю. Используя директиву use cache, преимущества Partial Prerendering и позволяя React Compiler обрабатывать низкоуровневые оптимизации, вы можете создавать приложения, которые будут не только быстрыми, но и отказоустойчивыми и удобными в поддержке.

Глядя в будущее веба в 2025 и 2026 годах, фокус сместился с вопроса «сколько мы можем отправить клиенту» на «как мало нам на самом деле нужно». Следуя этим лучшим практикам, вы гарантируете, что ваши приложения на Next.js останутся на переднем крае скорости и пользовательского опыта.

rocket_launch

Ready to start your project?

Let's discuss how I can help bring your ideas to life with modern web technologies and AI.

Get in Touch