← All Articles
Engineering10 min read

Next.js 15 App Router: 6 Lessons From 12 Months in Production

March 30, 202510 min read

Next.js 15 with the App Router is a genuinely great framework — but it has sharp edges that only show up in production. After shipping 8 projects on it, here's what we know.

1. Cache invalidation is still the hardest problem

The App Router's caching is powerful but opaque. revalidatePath doesn't do what most developers think — it invalidates the full route cache, not just the data cache. We've had multiple instances of stale UI persisting after data mutations because of incorrect cache assumptions.

Rule: be explicit about every cache boundary. Use no-store by default on fetch calls that involve user-specific data. Add cache tags to everything you might want to invalidate selectively.

2. Server components don't eliminate client bundle bloat

One of the App Router's promises is smaller client bundles. This is true — but only if you're disciplined about the client/server boundary. A single "use client" directive on a parent component pulls all its children into the client bundle. We use a pattern of wrapping interactive leaves in client components rather than marking entire sections.

3. Streaming + Suspense is production-ready

We were cautious about loading.tsx and Suspense boundaries in the first few months. Now we use them everywhere. The TTFB improvements on data-heavy pages are significant — users see layout immediately while data loads, which feels dramatically faster even if the total load time is similar.

4. Parallel routes are underused

Parallel routes (@slot convention) are one of the most powerful App Router features and almost nobody uses them. We use them for dashboard layouts where multiple independent data sections should load in parallel without a shared loading state.

5. Edge runtime has real limitations

Edge functions are fast, but the limited runtime means no native Node.js modules. We've hit this with PDF generation, image processing, and certain database drivers. Know your runtime requirements before going edge.

6. Type safety across the server/client boundary

Props passed from server to client components must be serialisable. We've added a lint rule to catch non-serialisable types (like Date objects, class instances) being passed across the boundary. Use plain objects and ISO strings.

GET STARTED

Ready to build
something exceptional?

From idea to launch in weeks, not months. Let's talk about your project.