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. Для типового проекта без потребности в этих фичах выгода миграции часто не покрывает затрат на переписывание.