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. Но это не бессмысленные изменения ради изменений: за каждым из них стоит конкретная проблема производительности или предсказуемости поведения.
Фреймворк взрослеет. Иногда болезненно, но в правильном направлении.
Комментарии
Чтобы оставить комментарий, войдите в аккаунт.