Next.js — App Router, SSR & caches
The App Router ties routing, React Server Components, data fetching, caching, streaming, metadata, and deployment runtime together. The senior skill is separating those concerns mentally even when the framework composes them in one file tree.
Core details
App Router primitives: app/ segments, layout, page, loading, error, not-found, route groups, parallel routes, and intercepting routes. Nested layouts persist across navigation, so state and error/loading boundaries must be placed intentionally.
Server Components by default: a component is server-rendered unless a "use client" boundary is introduced. Server components can fetch data and keep server-only code out of the client bundle. Client components hydrate, handle events, use state/effects, and can receive only serializable props.
Cache layers to separate:
| Layer | Stores | Main risk |
|---|---|---|
| Request Memoization | repeated function/fetch results during one server render | assuming it persists across users |
| Data Cache | fetched/cached data across requests | stale business data |
| Full Route Cache | rendered HTML/RSC output | stale personalization |
| Router Cache | client-side RSC payload by segment | soft navigation showing old server state |
| CDN/browser cache | HTTP responses/assets | wrong keying or unsafe public caching |
Rendering modes: static/prerendered output, request-time SSR, streaming with Suspense, and static shells with dynamic holes through newer cache/prerendering features. The fallback UI matters as much as the mechanism because poor skeletons create layout shift and user confusion.
Mutations: Server Actions and route handlers are server entrypoints. Authenticate, authorize, validate input, make writes idempotent where needed, and invalidate the right cache boundary after success.
Runtime choice: Edge runtime can reduce latency for lightweight request shaping, redirects, and auth checks, but Node runtime is often required for database drivers, file APIs, long-lived connections, and heavier libraries.
Understanding
Next’s App Router merges routing, data fetching, and the component graph tightly. Staff-level clarity separates CDN/full-route output, fetch/data cache policies, and client Router Cache behavior. Bugs often arrive as stale personalization: middleware saw auth, but a server component still rendered guest UI because the route or data was cached under the wrong assumptions.
Server Components are not an RPC mechanism from the browser. They are a server-rendered component graph serialized into an RSC payload. Client components receive serializable props from that graph, hydrate, and handle browser interactions.
Streaming is most valuable when the shell is useful and stable. If every important region suspends behind a spinner, streaming only moves the blank state around. Reserve dimensions, keep navigation usable, and align loading states with the user’s task.
Practical examples
| Requirement | Likely choice | Risk |
|---|---|---|
| Public marketing page | Static/prerendered with long-lived assets | Preview and CMS invalidation workflow |
| Auth dashboard | Dynamic render or carefully keyed cache | Stale or cross-user personalization |
| Product page with reviews | Static shell + dynamic reviews if acceptable | Inconsistent freshness messaging |
| Checkout mutation | Server Action or route handler with idempotency | Duplicate submit and stale router cache |
| Geolocation/auth middleware | Edge runtime if APIs fit | Node-only packages and cold-start assumptions |
Cache debugging order:
- Is the route static or dynamic?
- Is data cached, memoized per request, or uncached?
- Is the client Router Cache showing an old RSC payload?
- Is a CDN/browser cache serving old HTML or assets?
- Did a mutation call the right invalidation path?
Boundary placement rule: keep data-heavy display components on the server when possible; move only interactive controls, browser APIs, and stateful widgets behind "use client".
Senior understanding
| Lens | Speak to |
|---|---|
| Security | Never pass secrets into client props; avoid caching auth HTML publicly; validate Server Action inputs |
| Performance | RSC reduces shipped JS, but server waterfalls still hurt TTFB |
| Ops | Self-host vs managed platform, ISR/storage, runtime limitations, SSR error instrumentation |
| Correctness | Cache invalidation after mutations, deploy ordering for payload changes, deterministic hydration |
Trap answers: “RSC solves all data loading” without discussing serialization and TTFB; ignoring cold start and connection-pool realities; treating client cache as magically consistent with server caches.
Failure modes
- Adding
"use client"to a layout and accidentally hydrating the whole route subtree. - Caching a route that reads auth-like state without marking it dynamic or keying safely.
- Assuming
router.refresh()invalidates every server/CDN/data cache. - Creating sequential server fetch waterfalls that dominate TTFB.
- Passing class instances, functions, secrets, or non-serializable values across an RSC/client boundary.
Interview drill
Question: "A Next.js App Router page shows old data after a Server Action succeeds. Where do you look?"
Model answer structure:
- Name the cache layers separately: request memoization, Data Cache, Full Route Cache, Router Cache, CDN, and browser cache.
- Check the mutation result, whether the right tag/path was revalidated, and whether the client is showing an old Router Cache payload.
- Confirm the route's dynamic/static status and whether auth or cookies affect cache eligibility.
- Fix the narrow stale boundary: revalidate tag/path, refresh the client route, mark dynamic, or correct HTTP/CDN policy.
- Add a regression case that mutates, navigates away/back, and verifies fresh server state.
Follow-ups to expect:
- "What happens when
"use client"is placed too high?" - "Why do Server Components not automatically make TTFB fast?"
- "When would Edge runtime be the wrong choice?"
Diagram
See also
Mark this page when you finish learning it.
Spotted something unclear or wrong on this page?