Next.js
React
Web Development
Frontend

Next.js 15: Что нового и стоит ли обновляться

Полный обзор нововведений Next.js 15: стабильный Turbopack, улучшенные Server Actions, Partial Prerendering и новая модель кэширования. Руководство по миграции и честное сравнение производительности.

21 декабря 2025 г.
Команда QZX Studio
8 min read

Выход Next.js 15 стал одним из самых ожидаемых событий во фронтенд-экосистеме. Vercel не просто добавили новые фичи — они фундаментально переработали подход к кэшированию, сделали Turbopack стабильным и открыли дверь к Partial Prerendering. В этой статье мы разберём каждое нововведение, оценим реальное влияние на production-проекты и дадим чёткий план миграции с Next.js 14.

Turbopack: наконец стабильный

Turbopack находился в бета-тестировании с момента анонса на Next.js Conf 2022. Три года доработок привели к тому, что в Next.js 15 он получил статус стабильного для dev-режима.

Что изменилось на практике

Turbopack заменяет Webpack в качестве бандлера для разработки. Ключевые улучшения:

  • Холодный старт — до 76% быстрее по сравнению с Webpack
  • Fast Refresh — обновление кода в браузере до 96% быстрее
  • Инкрементальная компиляция — умное кэширование на уровне модулей

Для включения Turbopack достаточно добавить флаг:

next dev --turbopack

Или в package.json:

{
  "scripts": {
    "dev": "next dev --turbopack"
  }
}

Реальные бенчмарки

Мы протестировали Turbopack на нескольких наших проектах разного масштаба:

МетрикаWebpack (Next.js 14)Turbopack (Next.js 15)Разница
Холодный старт (малый проект)2.8 сек1.1 сек-61%
Холодный старт (большой проект)12.4 сек3.2 сек-74%
Fast Refresh320 мс45 мс-86%
Потребление RAM1.2 ГБ0.8 ГБ-33%

Важное замечание: Turbopack для production-сборки всё ещё находится в разработке. Для next build по-прежнему используется Webpack. Vercel обещают стабильный production Turbopack в одном из следующих минорных релизов.

Революция в кэшировании

Пожалуй, самое важное и спорное изменение в Next.js 15 — полный пересмотр стратегии кэширования. В Next.js 14 агрессивное кэширование по умолчанию было источником множества багов и путаницы у разработчиков.

Что было раньше (Next.js 14)

В предыдущей версии:

  • fetch() кэшировался по умолчанию (нужно было явно указывать cache: 'no-store')
  • Route Handlers с GET-запросами кэшировались по умолчанию
  • Client Router Cache агрессивно кэшировал страницы

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

Новый подход в Next.js 15

Теперь по умолчанию ничего не кэшируется:

// Next.js 14 — кэшируется по умолчанию
const data = await fetch('https://api.example.com/data');

// Next.js 15 — НЕ кэшируется по умолчанию
const data = await fetch('https://api.example.com/data');

// Next.js 15 — явное включение кэша
const data = await fetch('https://api.example.com/data', {
  cache: 'force-cache'
});

Аналогично для Route Handlers:

// Next.js 15 — GET-запросы больше не кэшируются по умолчанию
export async function GET() {
  const data = await getDataFromDB();
  return Response.json(data);
}

// Для кэширования нужно явно указать
export const dynamic = 'force-static';

Изменения в Client Router Cache

Client Router Cache теперь не кэширует Page-компоненты по умолчанию. Параметр staleTimes позволяет настроить поведение:

// next.config.ts
const nextConfig = {
  experimental: {
    staleTimes: {
      dynamic: 30, // секунды для динамических страниц
      static: 180, // секунды для статических страниц
    },
  },
};

Это изменение ломает обратную совместимость, но делает поведение фреймворка предсказуемым. Больше никаких «магических» кэшей, которые невозможно отладить.

Server Actions: улучшения безопасности

Server Actions в Next.js 15 получили важные доработки в плане безопасности и удобства использования.

Автоматическое удаление неиспользуемых Actions

Ранее все Server Actions получали публичный HTTP-эндпоинт, даже если они не использовались на клиенте. В Next.js 15 неиспользуемые actions автоматически удаляются из клиентского бандла с помощью tree-shaking.

Улучшенная обработка ошибок

Новый паттерн для обработки ошибок в Server Actions:

'use server';

import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  name: z.string().min(2),
});

export async function createUser(prevState: any, formData: FormData) {
  const validatedFields = schema.safeParse({
    email: formData.get('email'),
    name: formData.get('name'),
  });

  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
      message: 'Ошибка валидации',
    };
  }

  try {
    await db.user.create({ data: validatedFields.data });
    return { message: 'Пользователь создан', errors: null };
  } catch (error) {
    return { message: 'Ошибка при создании пользователя', errors: null };
  }
}

Интеграция с useActionState

React 19 принёс хук useActionState (ранее useFormState), который теперь полностью интегрирован с Server Actions:

'use client';

import { useActionState } from 'react';
import { createUser } from './actions';

export function CreateUserForm() {
  const [state, formAction, isPending] = useActionState(createUser, {
    message: '',
    errors: null,
  });

  return (
    <form action={formAction}>
      <input name="email" type="email" />
      {state.errors?.email && <p>{state.errors.email}</p>}

      <input name="name" type="text" />
      {state.errors?.name && <p>{state.errors.name}</p>}

      <button type="submit" disabled={isPending}>
        {isPending ? 'Создание...' : 'Создать'}
      </button>

      {state.message && <p>{state.message}</p>}
    </form>
  );
}

Partial Prerendering (PPR)

Partial Prerendering — это экспериментальная, но крайне перспективная фича, которая позволяет комбинировать статический и динамический рендеринг на одной странице.

Как это работает

Представьте страницу интернет-магазина:

  • Шапка, навигация, футер — статический контент, одинаковый для всех
  • Корзина, персональные рекомендации — динамический контент, уникальный для каждого пользователя

С PPR Next.js отдаёт статическую оболочку мгновенно, а динамические части подгружаются стримингом:

import { Suspense } from 'react';

export default function ProductPage() {
  return (
    <main>
      {/* Статическая часть — отдаётся мгновенно */}
      <ProductInfo />
      <ProductImages />
      <ProductDescription />

      {/* Динамическая часть — стримится отдельно */}
      <Suspense fallback={<CartSkeleton />}>
        <Cart />
      </Suspense>

      <Suspense fallback={<RecommendationsSkeleton />}>
        <PersonalRecommendations />
      </Suspense>
    </main>
  );
}

Для включения PPR необходимо добавить в конфигурацию:

// next.config.ts
const nextConfig = {
  experimental: {
    ppr: 'incremental',
  },
};

И активировать для конкретного роута:

// app/product/[id]/page.tsx
export const experimental_ppr = true;

Зачем это нужно

PPR решает извечную дилемму: статика быстрая, но не персонализированная; динамика персонализированная, но медленная. С PPR вы получаете лучшее из двух миров — скорость статики и гибкость динамики.

React 19 под капотом

Next.js 15 полностью переходит на React 19, что приносит набор новых возможностей.

Новые хуки

  • useActionState — управление состоянием серверных действий (заменил useFormState)
  • useFormStatus — получение статуса отправки формы в дочерних компонентах
  • useOptimistic — оптимистичные обновления UI

Пример оптимистичного обновления:

'use client';

import { useOptimistic } from 'react';
import { addComment } from './actions';

export function Comments({ comments }: { comments: Comment[] }) {
  const [optimisticComments, addOptimisticComment] = useOptimistic(
    comments,
    (state, newComment: string) => [
      ...state,
      { id: 'temp', text: newComment, pending: true },
    ]
  );

  async function handleSubmit(formData: FormData) {
    const text = formData.get('text') as string;
    addOptimisticComment(text);
    await addComment(text);
  }

  return (
    <div>
      {optimisticComments.map((comment) => (
        <div key={comment.id} style={{ opacity: comment.pending ? 0.5 : 1 }}>
          {comment.text}
        </div>
      ))}
      <form action={handleSubmit}>
        <input name="text" />
        <button type="submit">Отправить</button>
      </form>
    </div>
  );
}

Директива use client и use server

В React 19 директивы 'use client' и 'use server' стали официальной частью спецификации, а не фичей Next.js. Это важный шаг к стандартизации серверных компонентов в экосистеме React.

Руководство по миграции

Подготовка

Перед миграцией убедитесь, что ваш проект соответствует требованиям:

  • Node.js — минимум версия 18.18.0
  • React — обновление до React 19
  • TypeScript — минимум версия 5.0

Пошаговая миграция

Шаг 1: Обновление зависимостей

npm install next@15 react@19 react-dom@19
npm install -D @types/react@19 @types/react-dom@19

Шаг 2: Запуск codemod

Next.js предоставляет автоматические codemod для миграции:

npx @next/codemod@latest upgrade latest

Этот инструмент автоматически исправит:

  • Переименование useFormState в useActionState
  • Обновление импортов NextRequest для Geo и IP
  • Замена next/dynamic на React.lazy где возможно

Шаг 3: Ручные исправления

После запуска codemod проверьте следующее:

  • Все fetch() вызовы — добавьте cache: 'force-cache' где нужно кэширование
  • Route Handlers — добавьте export const dynamic = 'force-static' для кэшируемых маршрутов
  • Проверьте работу форм с новым useActionState

Шаг 4: Тестирование

# Запустите dev-сервер с Turbopack
next dev --turbopack

# Проверьте production-сборку
next build
next start

Breaking Changes: полный список

Критические изменения, на которые стоит обратить внимание:

  1. fetch() не кэшируется по умолчанию — самое важное изменение, может повлиять на производительность если не добавить явное кэширование
  2. Route Handlers не кэшируются по умолчанию — GET-эндпоинты нужно явно пометить как статические
  3. NextRequest.geo и NextRequest.ip удалены — используйте middleware или платформенные решения
  4. next/dynamic измененоsuspense prop удалён, используйте Suspense обёртку
  5. Минимальная версия React — 19 — необходимо обновить все React-зависимости

Стоит ли обновляться?

Когда стоит обновляться сейчас

  • У вас большой проект и вы страдаете от медленного dev-сервера — Turbopack даст ощутимое ускорение
  • Вы боролись с непредсказуемым кэшированием в Next.js 14
  • Вы хотите использовать React 19 и его новые хуки
  • Вы начинаете новый проект — однозначно стартуйте на Next.js 15

Когда лучше подождать

  • У вас стабильный production-проект без проблем — нет срочной необходимости
  • Вы активно используете библиотеки, которые ещё не поддерживают React 19
  • У вас сложная настройка Webpack с кастомными плагинами — Turbopack может не поддерживать все конфигурации

Наша рекомендация

Если у вас есть время на тестирование, обновляйтесь. Изменения в кэшировании делают фреймворк гораздо более предсказуемым, Turbopack реально ускоряет разработку, а React 19 приносит полезные паттерны. Но обязательно выделите время на проверку всех data-fetching сценариев — именно кэширование является главным источником потенциальных проблем при миграции.

Заключение

Next.js 15 — это зрелый и продуманный релиз. Vercel учли обратную связь от сообщества: убрали агрессивное кэширование, стабилизировали Turbopack, добавили экспериментальный PPR. Фреймворк движется в правильном направлении — к предсказуемому поведению и высокой производительности.

Если вам нужна помощь с миграцией на Next.js 15 или разработкой проекта на нём — команда QZX Studio готова помочь. Мы работаем с Next.js с первых версий и знаем все подводные камни.

Поделиться статьёй:

Нужна помощь с проектом?

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