Головна / Статті / 20 розширених шаблонів Next.js для додатків App Router виробничого рівня

20 розширених шаблонів Next.js для додатків App Router виробничого рівня

Дізнайтеся про двадцять передових патернів Next.js для досвідчених розробників, які охоплюють підхід «сервер на першому місці», стрімінг, кешування, маршрутизацію та оптимізацію продуктивності, щоб створювати швидші та масштабовані продакшн-додатки.

2928 слів

Більшість розробників обирають Next.js. Досвідчені інженери розуміють його основну філософію.

Якщо ви коли-небудь переглядали запит на з’єднання від когось, хто опанував 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 для завдань, чутливих до затримок

Проміжний сервіс автентифікації, персоналізація, функціональні прапорці — все, що має виконуватися з кожним надходженням запиту, отримує переваги від роботи на сервері, який знаходиться фізично близько до відвідувача. Саме це пропонує edge-середовище виконання.

export const runtime = 'edge'

Уявіть відвідувача в Мумбаї: з’єднання з edge-вузлом у Сінгапурі порівняно з з’єднанням з основним сервером у Вірджинії означає різницю приблизно в 20 мс проти 200 мс. Якщо така різниця буде присутня у достатньому обсязі трафіку, вона почне впливати на показники конверсії.

Проблема полягає у тому, що edge-середовище виконання підтримує значно обмеженіший набір 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: Структура Monorepo для великих команд

Як тільки кодова база Next.js починає обслуговувати більше ніж один продукт — скажімо, додаток для користувачів, внутрішню панель керування та сайт документації — виникає необхідність у прийнятті рішення. Ви можете тримати кожен з них у окремому репозиторії, що швидко стає проблемою для синхронізації, або можна об’єднати все в один Monorepo, що забезпечує єдине керування залежностями, спільні бібліотеки компонентів та єдиний процес 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-рантайму замість безсерверної функції, ви будете працювати на належному рівні мислення.

Наступний крок — це не запам’ятовування додаткових шаблонів. Це створення чогось реального з їх використанням у справжніх умовах обмежень — суворих термінів, конкуруючих пріоритетів, коду минулих версій, який неможливо просто переписати. Саме в такому середовищі ваші ментальні моделі проходять випробування під тиском, і саме там починає формуватися справжній судження.

Виберіть три шаблони, які є найважливішими для того, над чим ви зараз працюєте. Застосовуйте їх свідомо. Потім переходьте до наступних трьох.

Ось справжній шлях до того, щоб стати старшим інженером — не знати кожної можливої техніки, але мати глибоке розуміння тих, що справді мають значення.

Пов’язана література