Next.js 15 Production Architecture: App Router, PPR, and Caching at Scale
A practical guide to structuring a Next.js 15 application for production — Server Components, Partial Prerendering, cache invalidation, and deployment patterns.

Next.js 15 ships with a set of features that, used correctly, let you build applications that are simultaneously fast to load, cheap to serve, and easy to maintain. Used incorrectly, the same features create caching bugs that are painful to debug in production. This post covers the architecture decisions that matter.
The Mental Model: Where Does Code Run?
Before writing a single line, answer this for every component:
| Question | Answer → |
|---|---|
| Does this data change per-request? | Server Component with cache: 'no-store' |
| Does this data change on a schedule? | Server Component with revalidate |
| Does this data never change? | Server Component, fully static |
| Does this need interactivity? | Client Component ('use client') |
The App Router default is Server Components. Adding 'use client' is an explicit opt-in, not the starting point.
Folder Structure for a Real Application
app/
[locale]/
(public)/ ← layout with navbar/footer
page.tsx ← home, static
blog/
page.tsx ← listing, revalidate: 60
[slug]/page.tsx ← detail, static + ISR
portfolio/
page.tsx ← static
(protected)/ ← layout with auth check
dashboard/
page.tsx ← dynamic, no cache
api/
contact/route.ts ← POST, rate-limited
og/route.ts ← dynamic OG image
Route groups (public) and (protected) share different layouts without affecting the URL. This is cleaner than wrapping every protected page in an auth check.
Server Components: Data Fetching Patterns
Parallel fetching with Promise.all
Never await sequentially when requests are independent:
ts
Request deduplication with React cache
When multiple components on the same page need the same data, React.cache ensures the fetch runs once per render:
ts
Both <Header> and <Dashboard> can call getUser(id) — the database query executes once.
Partial Prerendering (PPR)
PPR is the most significant architecture change in Next.js 15. It lets a single route be partially static:
ts
The HTML shell containing <HeroSection> is cached at the edge. The dynamic parts stream in behind a Suspense boundary. The user sees content in under 100ms; personalized data arrives shortly after.
Cache Invalidation Strategy
Next.js 15 has three cache layers. Know which one to invalidate:
ts
The revalidateTag approach scales better than revalidatePath because you tag data at the fetch level, not the route level. One API call invalidates every page that used that data.
Middleware for i18n and Auth
Middleware runs at the edge before any page renders. Keep it minimal — it runs on every request:
ts
Avoid database calls in middleware. Check cookies or JWT claims only — anything heavier belongs in the page's Server Component.
Production Deployment Checklist
Before pushing to Vercel:
bash
Watch for:
- Server Components accidentally importing client-only packages (
window,localStorage) cookies()orheaders()called outside of a dynamic context — this opts the entire route out of static generation- Images without explicit
width/heightcausing layout shift
Conclusion
The App Router's power comes from understanding the static/dynamic boundary and placing it intentionally. Default to static, add Suspense boundaries for dynamic sections, tag your fetches for granular invalidation, and keep middleware thin. These four decisions cover 90% of production performance issues before they happen.
Похожие статьи
Restaurant Website with a Digital Menu: 2026 Best Practices
How restaurants win more guests with a fast website, a digital menu and online reservations — mobile-first, findable and appetising.
Читать статьюDentist Website 2026: Online Booking, Data Protection, Patient Trust
What a modern dental practice website needs: online appointment booking, GDPR-compliant handling of patient data, and trust that wins new patients.
Читать статью