Next.js 16: Честный разбор фреймворка, который снова всё переименовал

mr. Cooper 1 неделю назад Технологии
Next.js 16: Честный разбор фреймворка, который снова всё переименовал

Каждый раз, когда выходит новая версия Next.js, в сообществе разработчиков происходит одно и то же: половина людей в восторге, вторая половина судорожно проверяет, что именно сломалось в их проекте. Next.js 16 не стал исключением - но на этот раз изменения действительно заслуживают внимания, а не просто раздражения.

Я разобрал фреймворк от установки до деплоя, и вот что получилось на практике.

Почему React без Next.js уже неудобен

Прежде чем говорить о новых фичах, стоит понять, зачем Next.js вообще существует. React - это библиотека для построения интерфейсов, и в этой роли она прекрасна. Но как только проект выходит за рамки учебного приложения, начинаются вопросы: как организовать роутинг? Как настроить серверный рендеринг? Как разделить код на бандлы? Где хранить серверную логику?

На каждый из этих вопросов React отвечает примерно одинаково: «установи нужный пакет и настрой сам». Next.js отвечает иначе - он делает это за вас, причём делает хорошо.

Конкретные преимущества, которые вы получаете сразу после установки:

Файловый роутинг. Вместо того чтобы устанавливать React Router и описывать маршруты вручную, вы просто создаёте папку about внутри директории app - и маршрут /about готов. Вложенные маршруты, динамические параметры, группы маршрутов - всё это решается структурой файловой системы.

Автоматическое разделение кода. В React это требует ручной настройки через React.lazy и Suspense. В Next.js каждая страница получает только нужный ей JavaScript - без дополнительных телодвижений.

Оптимизация «из коробки». Шрифты, изображения, сторонние скрипты - всё это Next.js оптимизирует автоматически. Компонент Image сам применяет ленивую загрузку, компрессию и CDN. Шрифты предзагружаются, чтобы избежать layout shift.

Серверный рендеринг и SEO. Клиентский рендеринг отдаёт поисковым роботам практически пустую страницу. Серверный рендеринг Next.js отдаёт готовый HTML - и Google есть что индексировать.

Архитектура компонентов: сервер против клиента

Самое важное, что нужно понять в современном Next.js - разделение компонентов на серверные и клиентские. Это не просто синтаксический сахар, это принципиально другой способ думать об архитектуре приложения.

Серверные компоненты рендерятся на сервере. Они могут напрямую обращаться к базе данных, читать файловую систему, использовать секретные ключи - и ничего из этого не попадёт в браузер пользователя. По умолчанию каждый новый компонент в Next.js является серверным.

Клиентские компоненты выполняются в браузере. Они нужны тогда, когда компонент требует интерактивности: обработчики кликов, useState, useEffect, работа с DOM. Чтобы сделать компонент клиентским, достаточно добавить директиву 'use client' в начало файла.

Есть нюанс, который поначалу сбивает с толку: клиентские компоненты тоже предварительно рендерятся на сервере. Это называется серверным пре-рендерингом. Сервер создаёт статическую оболочку компонента, а браузер затем «гидратирует» её - то есть навешивает обработчики событий и заполняет интерактивные части.

Практическое правило простое: оставляйте компонент серверным до тех пор, пока не понадобится браузерная интерактивность. Как только вам нужен onClick или useState - добавляйте 'use client'. Часто имеет смысл вынести интерактивную часть в отдельный маленький компонент, оставив родительскую страницу серверной.

В Next.js 16 появилась встроенная поддержка React Compiler - он автоматически мемоизирует компоненты на этапе сборки, избавляя от необходимости вручную использовать useMemo и useCallback.

Роутинг: от простого к сложному

Файловый роутинг в Next.js работает на основе нескольких простых правил.

Базовые маршруты. Папка about с файлом page.tsx внутри создаёт маршрут /about. Специальный файл page.tsx - это то, что отображается по данному маршруту.

Вложенные маршруты. Папка dashboard с вложенными папками users и analytics создаёт маршруты /dashboard/users и /dashboard/analytics. Никакого конфига, только структура папок.

Динамические маршруты. Если часть URL должна быть переменной - например, страница конкретного пользователя - папку называют с квадратными скобками: [id]. Тогда по маршруту /dashboard/users/42 в компоненте будет доступен параметр id со значением 42. В Next.js 16 params - это промис, так что их нужно явно awaiting:

const { id } = await params;

Группы маршрутов - это папки в круглых скобках, например (dashboard) и (root). Они позволяют организовать файлы логически, не влияя на URL. Главная польза - возможность давать разным группам маршрутов разные лейауты. Публичные страницы с одним навбаром, дашборд с другим - и всё это в рамках одного приложения.

Специальные файлы дают дополнительные возможности:

  • layout.tsx - общая обёртка для всех маршрутов в папке

  • error.tsx - UI для отображения ошибок (должен быть клиентским компонентом)

  • loading.tsx - скелетон или спиннер во время загрузки данных

  • not-found.tsx - страница 404

В Next.js 16 добавились специальные файлы forbidden.tsx и unauthorized.tsx для более гранулярной обработки проблем с доступом.

Получение данных: правильный и неправильный способ

Это тот момент, где большинство разработчиков, приходящих из чистого React, делают ошибку - они продолжают использовать привычный паттерн с useEffect.

Неправильный способ (клиентский):

'use client';
const [albums, setAlbums] = useState([]);

useEffect(() => {
  const fetchAlbums = async () => {
    const res = await fetch('/api/albums');
    setAlbums(await res.json());
  };
  fetchAlbums();
}, []);

Проблемы: пользователь видит пустую страницу до загрузки данных, поисковики не могут проиндексировать контент, код избыточен.

Правильный способ (серверный):

const res = await fetch('https://api.example.com/albums');
const albums = await res.json();

Всё. Никаких хуков, никаких состояний. Страница рендерится уже с данными, SEO работает, кода в три раза меньше. Серверные компоненты могут напрямую обращаться к базе данных - без промежуточного API:

const posts = await prisma.post.findMany();
// или
const events = await Event.find().sort({ createdAt: -1 });

API-роуты и серверные действия

Next.js позволяет писать бэкенд-логику прямо внутри проекта, без отдельного сервера.

API-роуты - это файлы route.ts внутри папки app/api/. Они работают как бессерверные функции:

export async function GET() {
await connectDB();
  const events = await Event.find().sort({ createdAt: -1 });
  return NextResponse.json({ events }, { status: 200 });
}

export async function POST(request: NextRequest) {
  const formData = await request.formData();
  // обработка формы, загрузка файлов и т.д.
  return NextResponse.json({ message: 'Created' }, { status: 201 });
}

Серверные действия (Server Actions) - более элегантный инструмент для мутаций. Это обычные асинхронные функции с директивой 'use server', которые выполняются на сервере, но вызываются прямо из клиентского кода:

'use server';

export async function createBooking(eventId: string, email: string) {
  await connectDB();
  await Booking.create({ eventId, email });
  return { success: true };
}

Разница с API-роутами - никакого fetch, никаких запросов и ответов, никакого бойлерплейта. Вы вызываете функцию как функцию. Для операций чтения лучше подходят API-роуты или прямые обращения к базе в серверных компонентах; для мутаций - серверные действия.

Кэширование в Next.js 16: новая модель

Кэширование - самая переработанная часть фреймворка в этой версии. Старые концепции SSG, ISR и PPR никуда не делись, но теперь они стали результатом явных решений разработчика, а не отдельными режимами с отдельными настройками.

Главное нововведение - директива use cache. По умолчанию кэширование выключено, и вы сами решаете, что именно кэшировать. Включить новую систему можно в next.config.ts:

experimental: {
  cacheComponents: true
}

После этого директива use cache работает на уровне файла, компонента или функции:

'use cache';

import { cacheLife } from 'next/cache';

cacheLife('hours'); // кэшировать на час

const res = await fetch('/api/events');

Для управления кэшем есть два инструмента:

  • cacheLife - определяет срок жизни кэша

  • cacheTag - помечает данные тегами для точечной инвалидации

Когда нужны свежие данные, вызываете revalidateTag('events') - и только данные с этим тегом будут обновлены, остальной кэш остаётся нетронутым.

Интеграция с Partial Pre-Rendering тоже изменилась: статические части, помеченные use cache, рендерятся при сборке автоматически, а динамические части стримятся через React Suspense.

Build Adapters: конец зависимости от Vercel

В Next.js 16 появился новый API для адаптеров сборки - пока в альфа-версии, но это принципиальный сдвиг. До сих пор процесс сборки Next.js был тесно связан с инфраструктурой Vercel. Это логично - Vercel создал Next.js - но создавало ограничения для всех, кто хотел деплоить на AWS, Cloudflare, Netlify или собственные серверы.

Адаптеры позволяют хостинг-провайдерам и разработчикам напрямую встраиваться в pipeline сборки: изменять конфиг перед сборкой, трансформировать результат, адаптировать поведение под конкретную среду. Результат - деплой без конфигурации на любую платформу и независимость от одного провайдера.

SEO и метаданные

Next.js предоставляет два способа управления метаданными.

Конфигурационный подход - экспортировать объект metadata из файла страницы или лейаута:

export const metadata = {
  title: 'Dev Events',
  description: 'Хаб для всех технических событий'
};

Динамические метаданные - для страниц, где заголовок зависит от данных:

export async function generateMetadata({ params }) {
  const { slug } = await params;
  const event = await getEventBySlug(slug);
  return { title: event.title };
}

Файловый подход - разместить файлы с правильными именами (favicon.ico, og-image.png, robots.txt, sitemap.xml) в корне папки app, и Next.js автоматически создаст нужные мета-теги. Файловый подход имеет более высокий приоритет и переопределяет конфигурационный.

Типичные ошибки при деплое в Next.js 16

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

"Uncached data was accessed outside of Suspense" - возникает, когда флаг cacheComponents включён, а компонент обращается к динамическим данным без явного кэширования и без обёртки в Suspense. Решение: либо добавить use cache к компоненту или странице, либо вынести логику получения данных в дочерний компонент и обернуть его в <Suspense fallback={...}>.

params как промис - в новых версиях params в страницах - это промис. Паттерн, который работал раньше (const { slug } = params), теперь нужно заменить на const { slug } = await params.

Переменные окружения - NEXT_PUBLIC_ префикс нужен для переменных, доступных на клиентской стороне. Всё, что содержит секреты (строки подключения к БД, API-ключи) - без префикса, только для сервера.

Регион базы данных и хостинга - если MongoDB Atlas развёрнут в Европе, а функции Vercel выполняются в США, каждый запрос к БД добавляет 100–150 мс задержки. Выбирайте одинаковые или соседние регионы для существенного прироста скорости.

Итог

Next.js 16 - это не просто новая версия с переименованными API. Новая модель кэширования через use cache даёт то, чего давно не хватало: явный контроль над тем, что и как долго кэшируется, без магии за кулисами. Build Adapters открывают экосистему для других провайдеров. React Compiler убирает необходимость в ручной мемоизации.

Да, переход потребует переосмысления некоторых паттернов - особенно в части params как промисов и обёртки динамических компонентов в Suspense. Но это не бессмысленные изменения ради изменений: за каждым из них стоит конкретная проблема производительности или предсказуемости поведения.

Фреймворк взрослеет. Иногда болезненно, но в правильном направлении.

Комментарии

Пока нет комментариев. Будьте первым, кто напишет.

Чтобы оставить комментарий, войдите в аккаунт.

Похожие статьи