Vextrosys migrated eight production codebases from Pages Router to Next.js 15 App Router between 2024 and 2026 - marketing sites, authenticated dashboards, and e-commerce. The framework is mature enough for production, but the mental model shift trips teams. This is our consolidated playbook.

Server Components by default changes everything

Fetching in Server Components eliminates waterfalls to the client for read-heavy pages. The mistake is marking entire layouts 'use client' because one button needs state. Push client boundaries to leaves: interactive widgets, not page shells.

  • Fetch in RSC with fetch() caching semantics or unstable_cache for dedup
  • Pass serializable props to client children - no functions unless using Server Actions
  • Colocate loading.js and error.js per route segment for granular UX
// app/dashboard/page.tsx - server fetch
export default async function DashboardPage() {
  const stats = await getStats(); // cached, runs on server
  return <StatsClient initial={stats} />;
}

Caching: explicit beats magic

Next.js 15 made fetch caching opt-in by default in many cases - a good change that broke assumptions. Teams saw stale dashboards until they set cache: 'no-store' or revalidate tags intentionally. We document cache strategy per data type: static marketing (force-cache), catalog (revalidate 3600), user-specific (no-store).

revalidateTag is your friend

On CMS publish or product update, call revalidateTag('products') from a webhook route. Avoid global revalidatePath unless you understand blast radius on large sites.

Partial Prerendering (PPR)

Where enabled, PPR lets static shells stream while dynamic holes resolve - excellent for product pages with personalized pricing. Not every host supports it yet; verify Vercel vs. self-hosted Node constraints before architecting around PPR.

Server Actions: use cases and guardrails

Server Actions simplify mutations without API routes. We use them for form submissions, cart updates, and admin toggles. Always validate with Zod on the server, check auth inside the action, and rate-limit sensitive actions. Do not expose internal IDs without authorization checks - actions are callable endpoints.

  1. Validate input server-side - never trust client-only validation
  2. Use redirect() and revalidatePath/Tag after mutations
  3. Prefer actions for same-origin mutations; keep public APIs as route handlers for webhooks
  4. Log action failures with user id and correlation id

Authentication patterns

Middleware runs on Edge - keep it fast. We use NextAuth/Auth.js v5 or Clerk; session lookup in middleware, heavy authorization in server components or layout guards. Avoid fetching full user profile in middleware for every static asset request - matcher config matters.

export const config = {
  matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};
The App Router makes it easy to accidentally leak data in RSC props - if you fetch user A's data in a shared layout, verify tenant scoping on every query.

Performance wins we measured

  • Streaming SSR: TTFB improved 30-45% on content-heavy pages vs. client-only SPA
  • Image component + remotePatterns: LCP gains when migrating from custom loaders
  • Route-level code splitting automatic; still audit client bundle with @next/bundle-analyzer
  • Parallel routes for modals: cleaner URLs than global modal state in many cases

Migration pitfalls from Pages Router

  1. getServerSideProps → async Server Component or route handler
  2. next/head → metadata export in layout/page
  3. Dynamic routes: params are now async in Next 15 - await params
  4. Internationalization: plan middleware + segment structure early
  5. Third-party libs marked 'use client' - wrap in dynamic import ssr: false only when necessary

Testing

Playwright for E2E on critical flows; component tests for client islands. RSC unit testing is still awkward - invest in integration tests at route level.

Deployment and observability

OpenTelemetry via @vercel/otel or self-hosted collectors. Track RSC render time, external fetch latency, and action error rates. On AWS Amplify or container deploys, confirm Node 20+, sufficient memory for build, and ISR configuration on CDN.

App Router is our default for new Next projects in 2026. The learning curve is real; the payoff is faster perceived performance and simpler data fetching for content-heavy apps. Start with strict conventions on caching and client boundaries - conventions beat ad hoc 'use client' at the root.