Legan Studio
Все статьи
~ 6 мин чтения

Next.js App Router в продакшене: подводные камни

Реальные грабли App Router в Next.js — кеширование, серверные действия, границы клиент/сервер, миграция с Pages Router и нюансы рендеринга.

  • сайт
  • разработка
  • стек

App Router в Next.js — мощный инструмент, но за пару лет его стабилизации мы накопили солидный список граблей. Расскажу про те, на которые стоит обратить внимание прежде, чем выкатывать на прод.

Кеширование: не то, что вы думаете

App Router кеширует всё подряд по умолчанию: fetch-запросы, route-сегменты, full route cache. В версиях 13-14 это вызывало много путаницы — данные не обновлялись, разработчики не понимали почему.

В Next.js 15 поведение изменено: fetch теперь не кешируется по умолчанию, нужно явно указывать cache: "force-cache" или revalidate. Для GET-роутов handlers стало force-static, но это можно переопределить. Перед запуском в прод обязательно проверяйте, что критичные данные не залипают в кеше.

revalidatePath и revalidateTag — основные инструменты для сброса кеша. Используйте теги (fetch(url, { next: { tags: ["products"] } })) для гранулярной инвалидации.

Server Components и границы

Главное правило: данные тянем на сервере, интерактив — в клиентских компонентах. На практике границы расплываются. Например, нельзя передать функцию из Server Component в Client Component через props — только сериализуемые данные.

Импорт серверных утилит (например, import { db } from "@/lib/db") в клиентский компонент приведёт к попытке бандлинга серверного кода в клиент. Защита — пакет server-only: добавляете import "server-only" в файл, и любой клиентский импорт упадёт на этапе билда.

Серверные действия (Server Actions)

Server Actions — отличная замена API-роутам для форм. Но они имеют свои особенности: автоматически создаётся endpoint, нужно валидировать входные данные так же, как в API (Zod на сервере), нельзя считать клиентский payload безопасным.

Не используйте Server Actions для GET-операций — они всегда POST. Для чтения данных в клиенте делайте API Route или передавайте данные через Server Component.

Streaming и Suspense

Streaming с loading.tsx и <Suspense> — мощная фича, но требует правильного использования. Если у вас один длинный fetch на странице — стримить нечего. Streaming работает, когда страница состоит из нескольких независимых кусков, каждый со своим Suspense-границей.

Hydration mismatch — частая боль. Серверный и клиентский рендер должны выдавать одинаковый HTML. Date.now(), Math.random(), локализация дат через Intl без явной локали — типовые источники проблем. Решение: рендерить динамические значения только на клиенте через useEffect или suppressHydrationWarning для конкретных узлов.

Параллельные и перехватываемые маршруты

Parallel Routes (@slot) и Intercepting Routes ((.)photo/[id]) — мощные, но непривычные паттерны. Удобно для модальных окон, дашбордов с несколькими независимыми панелями. Но дебаг сложнее, и не все разработчики понимают, как это работает с первого раза.

Если вы только переходите на App Router — начните с базовых маршрутов и nested layouts. Parallel и Intercepting добавляйте, когда они реально нужны.

Edge Runtime и его ограничения

Некоторые роуты можно отдавать через Edge Runtime для низких задержек. Но Edge не поддерживает Node.js APIs целиком: нет fs, нет нативных модулей, нет долгих фоновых задач. Многие npm-пакеты (например, для PDF-генерации) сломаются.

Edge подходит для лёгкой обработки запросов, A/B-тестов, геолокации, простой авторизации. Для тяжёлой логики оставайтесь на Node runtime.

Миграция с Pages Router

Если у вас старый проект на Pages Router — миграция не обязательна. Pages Router всё ещё работает, поддерживается. Смешанная архитектура (часть на App, часть на Pages) тоже возможна, но усложняет общий стейт и middleware.

Полная миграция занимает 1-3 месяца на средний проект. Стоит делать, когда команде нужны Server Components, новый кеширующий слой или streaming.

Итого

App Router — отличный выбор для новых проектов, но требует понимания границ клиент/сервер, нюансов кеширования и нового подхода к данным. Перед продом — обязательный аудит кеш-стратегии, валидации Server Actions и hydration. Большинство граблей хорошо документированы, но всё равно ловят даже опытные команды.

Частые вопросы

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

App Router кеширует всё подряд по умолчанию: fetch-запросы, route-сегменты, full route cache. В версиях 13-14 это вызывало путаницу — данные не обновлялись, разработчики не понимали почему. В Next.js 15 поведение изменено: fetch теперь не кешируется по умолчанию, нужно явно указывать cache: "force-cache" или revalidate. Для GET-роутов handlers стало force-static, но это можно переопределить. Перед продом обязательно проверяйте, что критичные данные не залипают в кеше. revalidatePath и revalidateTag — основные инструменты.

Где граница между Server Components и Client Components?

Главное правило: данные тянем на сервере, интерактив — в клиентских компонентах. На практике границы расплываются. Например, нельзя передать функцию из Server Component в Client Component через props — только сериализуемые данные. Импорт серверных утилит (import { db } from "@/lib/db") в клиентский компонент приведёт к попытке бандлинга серверного кода в клиент. Защита — пакет server-only: добавляете import "server-only" в файл, и любой клиентский импорт упадёт на этапе билда.

Как правильно использовать Server Actions в Next.js?

Server Actions — отличная замена API-роутам для форм. Но они имеют свои особенности: автоматически создаётся endpoint, нужно валидировать входные данные так же, как в API (Zod на сервере), нельзя считать клиентский payload безопасным. Не используйте Server Actions для GET-операций — они всегда POST. Для чтения данных в клиенте делайте API Route или передавайте данные через Server Component. Это распространённая ошибка, которая приводит к лишним POST-запросам и неправильному кешированию.

Как избежать hydration mismatch в App Router?

Hydration mismatch — частая боль. Серверный и клиентский рендер должны выдавать одинаковый HTML. Date.now(), Math.random(), локализация дат через Intl без явной локали — типовые источники проблем. Решение: рендерить динамические значения только на клиенте через useEffect или suppressHydrationWarning для конкретных узлов. Также Streaming с loading.tsx и Suspense — мощная фича, но требует правильного использования. Если у вас один длинный fetch на странице — стримить нечего.

Когда использовать Edge Runtime в Next.js, а когда нет?

Некоторые роуты можно отдавать через Edge Runtime для низких задержек. Но Edge не поддерживает Node.js APIs целиком: нет fs, нет нативных модулей, нет долгих фоновых задач. Многие npm-пакеты (например, для PDF-генерации) сломаются. Edge подходит для лёгкой обработки запросов, A/B-тестов, геолокации, простой авторизации. Для тяжёлой логики оставайтесь на Node runtime. Не пытайтесь сделать «всё на Edge» — это типичная ошибка, приводящая к проблемам с зависимостями.

Стоит ли мигрировать с Pages Router на App Router?

Если у вас старый проект на Pages Router — миграция не обязательна. Pages Router всё ещё работает, поддерживается. Смешанная архитектура (часть на App, часть на Pages) тоже возможна, но усложняет общий стейт и middleware. Полная миграция занимает 1-3 месяца на средний проект. Стоит делать, когда команде нужны Server Components, новый кеширующий слой или streaming. Для типового проекта без потребности в этих фичах выгода миграции часто не покрывает затрат на переписывание.