Продакшн-архітектура Next.js 15: App Router, PPR і масштабоване кешування
Практичний посібник зі структури Next.js 15 застосунку для продакшену: Server Components, Partial Prerendering, інвалідація кешу та патерни розгортання.

Це технічна стаття для розробників. Якщо ви керуєте салоном, рестораном або ремісничим бізнесом, почніть краще звідси: скільки насправді коштує сайт і що справді потрібно вашому сайту.
Next.js 15 пропонує набір можливостей, які за правильного використання дають змогу створювати застосунки, що водночас швидко завантажуються, недорого обслуговуються й легко підтримуються. За неправильного використання ті самі можливості породжують проблеми з кешуванням, які болісно налагоджувати в продакшені. У цій статті розглянемо архітектурні рішення, які справді мають значення.
Ментальна модель: де виконується код?
Перш ніж написати хоча б один рядок, дайте відповідь на це питання для кожного компонента:
| Питання | Відповідь → |
| ----------------------------------- | --------------------------------------- |
| Дані змінюються для кожного запиту? | Server Component із cache: 'no-store' |
| Дані змінюються за розкладом? | Server Component із revalidate |
| Дані ніколи не змінюються? | Повністю статичний Server Component |
| Потрібна інтерактивність? | Client Component ('use client') |
За замовчуванням App Router використовує Server Components. Додавання 'use client' — це явний вибір, а не вихідна точка.
Структура каталогів для реального застосунку
app/
[locale]/
(public)/ ← layout із navbar/footer
page.tsx ← головна, статична
blog/
page.tsx ← список, revalidate: 60
[slug]/page.tsx ← детальна сторінка, static + ISR
portfolio/
page.tsx ← статична
(protected)/ ← layout із перевіркою auth
dashboard/
page.tsx ← динамічна, без кешу
api/
contact/route.ts ← POST, з rate limit
og/route.ts ← динамічне OG-зображення
Route groups (public) і (protected) використовують різні layouts, не впливаючи на URL. Це чистіше, ніж обгортати кожну захищену сторінку в перевірку auth.
Server Components: патерни отримання даних
Паралельне отримання через Promise.all
Не використовуйте послідовні await, коли запити незалежні:
// ❌ 600ms (два послідовні запити) const projects = await getProjects(); const testimonials = await getTestimonials(); // ✅ 300ms (паралельно) const [projects, testimonials] = await Promise.all([getProjects(), getTestimonials()]);ts
Дедуплікація запитів через React cache
Коли кільком компонентам на одній сторінці потрібні ті самі дані, React.cache гарантує, що fetch виконається один раз за render:
import { cache } from 'react'; export const getUser = cache(async (id: string) => { return prisma.user.findUnique({ where: { id } }); });ts
І <Header>, і <Dashboard> можуть викликати getUser(id), але запит до бази даних виконається лише один раз.
Partial Prerendering (PPR)
PPR — найсуттєвіша архітектурна зміна в Next.js 15. Вона дає змогу зробити один route частково статичним:
// next.config.ts export default { experimental: { ppr: 'incremental', }, }; // app/[locale]/page.tsx export const experimental_ppr = true; export default function HomePage() { return ( <> <HeroSection /> {/* статична оболонка — миттєво віддається з CDN */} <Suspense fallback={<ProjectsSkeleton />}> <FeaturedProjects /> {/* динамічна частина — стримиться після оболонки */} </Suspense> </> ); }ts
HTML-оболонка з <HeroSection> кешується на edge. Динамічні частини надходять потоком усередині Suspense boundary. Користувач бачить контент менш ніж за 100 ms, а персоналізовані дані з’являються трохи пізніше.
Стратегія інвалідації кешу
У Next.js 15 є три рівні кешу. Важливо розуміти, який із них потрібно інвалідувати:
import { revalidatePath, revalidateTag } from 'next/cache'; // Інвалідувати конкретний URL, наприклад після публікації допису await revalidatePath('/blog/[slug]', 'page'); // Інвалідувати всі сторінки з тегом 'projects' — рекомендовано для даних await revalidateTag('projects'); // Позначити fetch тегом, щоб потім інвалідувати його за цим тегом const data = await fetch('/api/projects', { next: { tags: ['projects'], revalidate: 300 }, });ts
Підхід із revalidateTag краще масштабується, ніж revalidatePath, оскільки ви позначаєте дані на рівні fetch, а не route. Один API-виклик інвалідує кожну сторінку, яка використала ці дані.
Middleware для i18n і auth
Middleware виконується на edge до рендерингу сторінки. Залишайте його мінімальним — він працює для кожного запиту:
// middleware.ts import { NextRequest, NextResponse } from 'next/server'; import createIntlMiddleware from 'next-intl/middleware'; import { routing } from '@/i18n/routing'; const intlMiddleware = createIntlMiddleware(routing); export function middleware(request: NextRequest) { // Перевірка auth для захищених routes const isProtected = request.nextUrl.pathname.startsWith('/dashboard'); if (isProtected) { const session = request.cookies.get('next-auth.session-token'); if (!session) { return NextResponse.redirect(new URL('/login', request.url)); } } return intlMiddleware(request); }ts
Уникайте запитів до бази даних у middleware. Перевіряйте лише cookies або JWT claims — усе важче має бути в Server Component сторінки.
Чекліст розгортання в продакшн
Перед push у Vercel:
# 1. Перевірка типів npx tsc --noEmit # 2. Lint npm run lint # 3. Локальний build для виявлення помилок статичної генерації npm run build # 4. Перевірка розміру bundle ANALYZE=true npm run buildbash
Звертайте увагу на:
- Server Components, які випадково імпортують пакети лише для клієнта —
window,localStorage - Виклики
cookies()абоheaders()поза динамічним контекстом — це вимикає статичну генерацію для всього route - Зображення без явних
width/height, які спричиняють layout shift
Висновок
Сила App Router походить із розуміння межі між статичним і динамічним кодом та її свідомого розташування. За замовчуванням обирайте статичний рендеринг, додавайте Suspense boundaries для динамічних секцій, позначайте fetch-запити тегами для точкової інвалідації та не перевантажуйте middleware. Ці чотири рішення запобігають 90% проблем із продуктивністю в продакшені ще до їх появи.
Працюєте над чимось подібним?
Я створюю швидкі маркетингові сайти й системи онлайн-запису для малого бізнесу по всій Європі — як незалежний фахівець, віддалено. Відповідаю протягом 24 годин.
Зв’язатися