20 продвинутых шаблонов Next.js для приложений App Router высокого уровня
Изучите двадцать передовых паттернов Next.js для продвинутого уровня, охватывающих подход «сервер первым», стриминг, кэширование, маршрутизацию и оптимизацию производительности, чтобы создавать более быстрые и масштабируемые приложения для производства.
Большинство разработчиков учатся работать с Next.js. Опытные инженеры понимают его основную философию.
Если вы когда-либо рассматривали pull request от человека, который освоил Next.js исключительно благодаря учебным видео, вы, вероятно, заметили определенную закономерность: всё оборачивается в клиентский компонент.
Это не лень. Это просто то, что показывали в учебниках. useState, useEffect, 'use client' — они расположены в каждом файле, словно стандартные добавки. Это работает. Продукт можно развернуть. Но спустя несколько месяцев размер JavaScript-бундла превышает 400 КБ, показатели Core Web Vitals становятся красными, и команда не может понять, почему простая маркетинговая страница работает медленнее, чем нативное приложение.
Именно эта пропасть отделяет тех, кто использует Next.js, от инженеров, которые действительно понимают его.
С появлением App Router Next.js превратился из фреймворка React в нечто, близкое к полноценной платформе для разработки приложений. Старые предположения больше не актуальны. Концепции, считавшиеся «сложными» в версии Next.js 13, теперь являются стандартом. А методы, определяющие качественный код, готовый к использованию в производстве — подход с сервером во главе, тщательно спланированные стратегии кэширования, выполнение на периферии, частичная отрисовка — редко встречаются в учебных материалах, ориентированных на начинающих.
В этом материале рассматриваются эти методы. Всего двадцать шаблонов, каждый из которых сопровождается объяснением его важности.
Часть 1: Думайте сначала о сервере, а не о компонентах
Самое важное изменение в подходе к работе с современным Next.js заключается в следующем: начальной точкой должен быть сервер, а не клиент.
Большинство разработчиков на React сначала мыслят в терминах компонентов. Они инстинктивно обращаются к хукам, состоянию и логике, работающей на стороне браузера, потому что именно так их учили изначально. App Router противостоит этому инстинкту. Настоящий вопрос не в том, «должна ли эта часть поддерживать интерактивность?», а в том, «есть ли у этой части вообще причины выполняться в браузере?»
Шаблон 1: Серверные компоненты в качестве стандарта
export default async function Posts() {
const posts = await db.posts.findMany()
return <PostList posts={posts} />
}
Здесь нет useEffect. Нет запросов к API, запускаемых с клиента. Нет индикатора загрузки для данных, которые могли бы уже быть получены до отрисовки страницы.
Компоненты сервера означают меньший объем JavaScript-кода, отправляемого в браузер, более надежные гарантии безопасности (поскольку учетные данные базы данных никогда не покидают серверную среду), а также более быструю загрузку страницы. Основной принцип прост: если элемент интерфейса не требует взаимодействия, нет причин выполнять его на стороне клиента.
Шаблон 2: Соблюдение границы сервер/клиент
Именно здесь разработчики чаще всего допускают ошибки. Граница, разделяющая серверный код и клиентский код, — это не просто концептуальное руководство; система выполнения активно её соблюдает.
Функции не могут передаваться через эту границу. То же касается экземпляров классов. Подключения к базе данных абсолютно запрещены. Через неё могут проходить только данные, которые можно сериализовать.
// ✅ Fine — posts is plain JSON
<ClientComponent posts={posts} />
// ❌ Will explode — passing a function as a prop to a client component
<ClientComponent onFetch={db.posts.findMany} />
Если с самого начала понимать этое правило, это поможет избежать целой категории сбоев во время выполнения, отследить причины которых бывает чрезвычайно сложно.
Шаблон 3: Стратегическое использование 'use client'
Каждый раз, когда вы добавляете директиву 'use client', вы платите определенную цену:
- Больше JavaScript-кода попадает в бандл
- Дополнительная работа по загрузке приложения при открытии страницы
- Увеличенное потребление памяти в браузере во время выполнения
Подход, на котором основываются опытные инженеры, — это максимально глубокое размещение интерактивной логики в дереве компонентов, желательно в самых мелких листовых компонентах.
Page (Server)
├─ ProductList (Server)
├─ ProductDetails (Server)
└─ AddToCartButton (Client) ← only what truly needs the browser
Все остальное остается на сервере. Это не преждевременная оптимизация ради нее самой — это просто разумный архитектурный подход.
Часть 2: Отрисовка, которая не заставляет пользователей ждать
Шаблон 4: Интерфейс с потоковой отрисовкой и механизмом Suspense
Ничто так не снижает воспринимаемую скорость, как метод отрисовки «водопадом». Схема здесь привычная: сначала пустая страница, затем индикатор загрузки, а потом всё появляется на экране одновременно, как только готова каждая часть данных.
Next.js решает эту проблему с помощью потоковой отрисовки в сочетании с механизмом Suspense.
export default function ProductPage() {
return (
<>
<HeroSection /> {/* renders immediately */}
<Suspense fallback={<Skeleton />}>
<ProductList /> {/* streams in */}
</Suspense>
<Suspense fallback={<ReviewSkeleton />}>
<Reviews /> {/* streams in independently */}
</Suspense>
</>
)
}
Посетители получают визуальную обратную связь за долю секунды. Интерфейс формируется постепенно, вместо того чтобы застревать на самом медленном запросе.
Считайте это стандартным подходом для любого компонента, который зависит от запроса к базе данных или внешнему API.
Шаблон 5: Частичная предварительная отрисовка (PPR)
Частичная предварительная отрисовка — одна из самых интересных идей, введённых командой Next.js в последнее время.
Раньше приходилось выбирать один из вариантов: полностью статические страницы, которые загружаются быстро, но могут показывать устаревшие данные, или полностью динамические страницы, которые всегда актуальны, но загружаются медленнее. PPR устраняет необходимость выбора между ними. Один маршрут может сочетать оба режима — части, которые не меняются, передаются в CDN, а те части, которые меняются, передаются в прямом потоке с сервера.
┌─────────────────────────────────┐
│ Hero (static shell — CDN) │
│ Navbar (static shell — CDN) │
├─────────────────────────────────┤
│ User Dashboard (dynamic) │ ← streamed from server
│ Recommendations (dynamic) │ ← streamed from server
└─────────────────────────────────┘
Статическая часть страницы появляется мгновенно, а динамические элементы заполняют её по мере обработки. С точки зрения пользователя страница кажется быстрой. С точки зрения инфраструктуры большая часть контента предоставляется дешево благодаря кэшированию. Оба требования выполняются одновременно.
Часть 3: Маршрутизация за пределами основ
Маршрутизация на основе файлов — это общеизвестный факт среди разработчиков Next.js. Гораздо меньше людей исследовали, на что на самом деле способна система маршрутизации, когда речь идет не о базовых функциях.
Шаблон 6: Группы маршрутов для разделения доменов
Группы маршрутов позволяют структурировать папки проекта так, чтобы они не отображались в URL. Весь механизм заключается в том, что название папки помещается в скобки.
app/
├─ (marketing)/
│ ├─ page.tsx → /
│ └─ about/page.tsx → /about
├─ (dashboard)/
│ └─ analytics/ → /analytics
└─ (auth)/
└─ login/ → /login
Каждая группа может иметь собственную структуру, собственное состояние загрузки и собственные границы обработки ошибок. Это необходимо не только ради порядка — в крупной базе кода именно это предотвращает переплетение различных доменов приложения.
Шаблон 7: Параллельные маршруты для сложных лейаутов
Интерфейсы в стиле панели управления часто требуют одновременной отрисовки нескольких независимых источников данных. Именно для таких ситуаций созданы параллельные маршруты.
app/dashboard/
├─ layout.tsx
├─ @metrics/
│ └─ page.tsx
├─ @activity/
│ └─ page.tsx
└─ @notifications/
└─ page.tsx
Каждый именованный слот загружается и отображается по собственному графику. Медленная загрузка контента в одном слоте не замедляет остальные, поэтому пользователи видят каждый фрагмент данных сразу, как только он становится доступным.
Шаблон 8: Перехват маршрута для модальных окон
Это механизм, лежащий в основе интеракции, которую вы, вероятно, видели на Instagram, Pinterest и множестве онлайн-магазинов: при нажатии на миниатюру товара появляется модальное окно поверх текущей страницы. Однако при перезагрузке той же страницы открывается полноценный, автономный вид товара. Один и тот же URL, но два разных формата отображения в зависимости от способа доступа.
Click product card → modal overlay (fast, in-context)
Refresh / share URL → full product page (SEO-friendly)
Такое поведение называется перехватом маршрута. Именно эта техника делает приложение плавным и интуитивно понятным, в отличие от тех, где каждое нажатие сбрасывает контекст и переносит пользователя на совершенно новую страницу.
Часть 4: Кэширование с целью, а не случайно
Кэширование в Next.js раньше часто создавало проблемы, поскольку большая его часть происходила незаметно для пользователя. Иногда получались устаревшие данные там, где ожидались свежие результаты, а иногда наоборот — основная логика кэширования не была очевидна из написанного кода.
Современный подход позволяет целенаправленно настраивать кэширование на довольно тонком уровне. Овладев этими двумя подходами, большая часть прежних замешательств исчезнет.
Шаблон 9: Умное кэширование результатов запросов
// Fresh every 60 seconds
fetch('/api/posts', { next: { revalidate: 60 } })
// Never cache — always fresh
fetch('/api/user', { cache: 'no-store' })
// Static — cache forever until manually invalidated
fetch('/api/config', { cache: 'force-cache' })
Рассматривайте это как осознанное решение, а не как параметр по умолчанию, который оставляют без изменений. Важный вопрос заключается в том, какое количество устаревания может выдержать определенный набор данных, прежде чем это станет реальной проблемой для пользователя — значение, на которое ответит этот вопрос, и будет вашим параметром revalidate.
Шаблон 10: Аннулирование кэша по тегам
Когда данные взаимозависимы, для их обновления подходит тонкая настройка процесса аннулирования кэша. Присвойте теги вызовам функции fetch, а затем очищайте кэш для конкретного тега каждый раз, когда меняются соответствующие данные.
// Tag the fetch
fetch('/api/posts', { next: { tags: ['posts'] } })
// Later, in a Server Action after a post is created:
revalidateTag('posts')
Правильное аннулирование кэша — это действительно сложная проблема в любой распределенной системе. Next.js предоставляет надежный инструмент для её решения; используйте его, а не пытайтесь обойти проблему другими способами.
Часть 5: Мутации, проектирование API и местоположение логики
Шаблон 11: Использование серверных действий вместо API-маршрутов
Раньше обработка такой простой операции, как отправка формы, требовала синхронной работы нескольких компонентов: клиентского компонента, вызова функции fetch внутри него, отдельного обработчика POST для приема запроса и дублирования механизмов обработки ошибок на обеих сторонах.
Сегодня вся эта процедура сводится к гораздо меньшему количеству кода:
'use server'
export async function createPost(data: FormData) {
await db.post.create({ title: data.get('title') })
revalidateTag('posts')
}
Вы вызываете это непосредственно изнутри компонента. Не требуется определение отдельного API-маршрута и нет необходимости в повторяющейся структуре кода — фреймворк сам занимается всеми аспектами обработки HTTP-запросов.
Преимущество заключается не только в уменьшении количества вводимых данных. Это также сокращает количество мест, где может возникнуть сбой. Меньше файлов и меньше компонентов, участвующих в работе, напрямую приводит к меньшему количеству ошибок.
Шаблон 12: Оптимистичный интерфейс
Интерфейс, который кажется медленным, обычно ожидает ответа с сервера перед тем, как отобразить любые изменения. Решение заключается в простой последовательности действий: сразу обновить интерфейс, на фоне проверить изменения у сервера и, в случае неудачи проверки, вернуть интерфейс к прежнему состоянию.
User clicks Like
↓
UI updates instantly (optimistic)
↓
Server Action runs
↓
Success → confirm | Failure → rollback
Этот подход работает хорошо: он придает веб-приложениям ощущение, сравнимое с нативными приложениями. Однако без надлежащего механизма возврата к предыдущему состоянию это приводит к обратному эффекту — пользователи начинают замечать, что то, что они видят на экране, не совпадает с тем, что действительно было сохранено, и такое несоответствие подрывает доверие к приложению.
Шаблон 13: Обработчики маршрутов в качестве слоя API
Server Actions не являются подходящим инструментом для всех задач. Webhooks, интеграции с сервисами сторонних разработчиков и мобильные клиенты требуют стандартных REST-конечных точек, и именно это обеспечивают обработчики маршрутов.
// app/api/posts/route.ts
export async function GET() {
const posts = await db.posts.findMany()
return Response.json(posts)
}
Используйте Server Actions только для мутаций, запускаемых из собственного интерфейса пользователя. Обращайтесь к обработчикам маршрутов в тех случаях, когда необходимо выполнить вызов извне вашего приложения Next.js.
Часть 6: Производительность — не второстепенный фактор
Шаблон 14: Среда Edge Runtime для задач, чувствительных к задержкам
Промежуточные компоненты аутентификации, персонализация, флаги функций — всё, что должно выполняться при каждом входящем запросе, выигрывает от работы на сервере, расположенном физически близко к посетителю. Именно это предоставляет среда выполнения на периферии.
export const runtime = 'edge'
Возьмём посетителя в Мумбаи: подключение к узлу на периферии в Сингапуре по сравнению с подключением к основному серверу в Вирджинии даёт разницу примерно в 20 мс и 200 мс соответственно. Если таких запросов достаточно, эта разница начинает сказываться на показателях конверсии.
Проблема в том, что среда выполнения на периферии поддерживает гораздо более ограниченный набор API. Использование корневых модулей Node.js запрещено, а доступ к файловой системе отсутствует. Перед тем как полагаться на такой код, убедитесь, что он действительно работает в этих условиях.
Шаблон 15: Промежуточные компоненты для решения общих задач
Мидлвэр выполняется до отображения любого маршрута, что делает его идеальным местом для проверки авторизации, логики управления функциями, перенаправлений с учетом локализации и маршрутизации для тестов типа A/B.
export function middleware(req: NextRequest) {
const token = req.cookies.get('auth-token')
if (!token) return NextResponse.redirect(new URL('/login', req.url))
}
Сохраняйте логику внутри мидлвэра краткой. Поскольку он запускается при каждом запросе, любые ресурсоемкие операции, добавленные туда, увеличивают задержку во всем приложении.
Шаблон 16: API метаданных для эффективного SEO
Рендеринг с клиентской стороны раньше негативно влиял на SEO — заголовки обновлялись с помощью document.title уже после этого, а мета-теги вставлялись после загрузки — из-за чего поисковые роботы либо совсем не замечали эти изменения, либо индексировали несогласованные версии страницы.
API метаданных возвращает эту ответственность на сервер, куда она и принадлежит.
export const metadata = {
title: 'Product Name | Store',
openGraph: {
title: 'Product Name',
description: 'Product description',
images: ['/og-image.jpg'],
},
}
// Or dynamic:
export async function generateMetadata({ params }) {
const product = await getProduct(params.id)
return { title: product.name }
}
Если ваше приложение основано на контенте, рассматривать это как необязательное действие вообще невозможно.
Шаблон 17: Мониторинг Web Vitals
Невозможно исправить то, что нельзя измерить. Next.js предоставляет встроенный хук для сбора данных о производительности непосредственно из браузеров пользователей.
export function reportWebVitals(metric) {
// Send to your analytics platform
analytics.track(metric.name, { value: metric.value })
}
Три показателя, которые стоит отслеживать, — это LCP (скорость отрисовки крупнейшего видимого элемента), FID (скорость реакции страницы на первое взаимодействие пользователя) и CLS (степень смещения элементов после загрузки). Именно эти показатели учитывает Google при формировании рейтингов в поиске, и они также определяют то, что пользователи воспринимают как скорость или медлительность работы сайта.
Шаблон 18: Анализ пакетов
Прежде чем пытаться улучшить производительность, проверьте, что на самом деле отправляется в браузер.
ANALYZE=true next build
Результаты часто становятся неожиданными для команд. Часто встречаются дублирующиеся зависимости, код, который никогда не выполняется на критическом пути, или тяжелые библиотеки, у которых существуют более легкие альтернативы. Например, импорт moment.js может увеличить размер пакета на 70 КБ, в то время как его реальный размер должен составлять 30 КБ. Вы не узнаете об этом, пока не проверите самостоятельно.
Часть 7: Архитектура в масштабе
Шаблон 19: Структура монорепозитория для крупных команд
Как только кодовая база Next.js начинает обслуживать более одного продукта — например, приложение для публики, внутренний административный панель управления и сайт с документацией — перед вами возникает выбор. Вы можете хранить каждый из них в отдельном репозитории, что быстро превращается в проблему с синхронизацией, или же объединить всё в один монорепозиторий, что позволяет использовать единое управление зависимостями, общие библиотеки компонентов и единую систему CI.
apps/
├─ web/ → customer app
├─ admin/ → internal tools
└─ docs/ → documentation
packages/
├─ ui/ → shared component library
├─ config/ → shared TS/ESLint/Tailwind config
└─ types/ → shared TypeScript types
Сочетайте эту структуру с Turborepo для кэширования результатов сборки и PNPM для управления рабочими пространствами. На настройку уйдет примерно день работы, но со временем это окупится за счет исключения повторной работы и различий между проектами.
Шаблон 20: Мышление в области проектирования систем
Вот что действительно отличает опытного инженера Next.js от начинающего: это мало связано с тем, запомнили ли они правила использования Suspense или синтаксис Server Actions. Самые способные разработчики могут быстро освоить такой синтаксис.
Настоящее отличие заключается в том, как они мыслят о системе в целом.
Опытные инженеры разрабатывают стратегию кэширования ещё до написания любой логики загрузки данных. Они определяют местоположение границы между сервером и клиентом до создания компонентов. Они устанавливают лимиты производительности ещё до того, как начнут работать с JavaScript. Их основной вопрос звучит как «где должен выполняться этот код и почему?», а не основание на привычке.
Next.js вышел за рамки простого фреймворка для отрисовки — теперь он служит платформой для архитектуры приложений. Вы можете напрямую включать решения, связанные с полным стеком, в слой приложения: где происходит обработка данных, когда обновляются данные, как отрисовывается каждая страница. Нет необходимости собирать из отдельных бэкенд-сервисов что-то вроде «швейного куска», чтобы получить такой контроль.
Это означает настоящий сдвиг в понимании того, что включает в себя инженерия фронтенда. Разработчики, которые усваивают этот подход, создают системы, которые работают быстрее, требуют меньше ресурсов для эксплуатации и проще в обслуживании со временем. Те, кто этого не делает, склонны использовать клиентские компоненты везде и потом удивляются, почему приложение работает медленно.
Куда двигаться дальше
Эти двадцать шаблонов не предназначены для использования как список задач, которые нужно выполнить. Они представляют собой общий словарь терминов.
Как только вы сможете точно обсуждать границы между сервером и клиентом, разрабатывать стратегии кэширования для страниц с большим объемом контента или обосновывать выбор рантайма типа edge вместо безсерверных функций, вы будете мыслить на правильном уровне.
Следующим шагом не является запоминание дополнительных шаблонов. Это создание чего-то реального с их использованием в условиях реальных ограничений — строгих сроков, конкурирующих приоритетов, устаревшего кода, который нельзя просто переписать. Именно в такой среде проходят проверку ваши умственные модели, и начинает формироваться настоящее суждение.
Выберите три шаблона, которые наиболее важны для текущей работы. Применяйте их целенаправленно. Затем переходите к следующим трем.
Вот настоящий путь к статусу старшего инженера — не знать каждую возможную технику, но обладать глубоким владением теми, что действительно важны.
Связанные материалы
- Три шаблона TypeScript, улучшающих архитектуру React-приложений — Узнайте, как шаблоны Repository, Observer и Builder используют систему типов TypeScript для создания более чистых и удобных в обслуживании кодовых баз для React и Next.js.
- Миграция API Express в обработчики маршрутов App Router Next.js — Узнайте, как преобразовать маршруты Express, промежуточные компоненты и шаблоны обработки данных в App Router Next.js с использованием серверных компонентов, а также о соображениях по развертыванию.