Next.js rendering & caching
Static by default, dynamic when you touch a dynamic API
An App Router route is prerendered at build time unless something forces it to render per request. You do not pick static or dynamic; Next infers it from the APIs you call. Reading a dynamic API (cookies(), headers(), draftMode(), or the searchParams page prop, all async in Next 15) switches the entire route to dynamic. The params prop is also async in Next 15, but reading it does not force dynamic rendering; it resolves statically at build through generateStaticParams. So does a single fetch(url, { cache: 'no-store' }) or the config export const dynamic = 'force-dynamic'.
The rule people miss: one dynamic API taints the whole route, not the component that called it. A lone await cookies() in a nested server component makes the whole page render per request.
The four caches and where each one lives
Next stacks four independent caches along the path from click to database:
| Cache | Stores | Where | Lifetime |
|---|---|---|---|
| Request Memoization | fetch return values | server | one render pass |
| Data Cache | fetch/data results | server | persistent, survives redeploys |
| Full Route Cache | HTML + RSC payload | server | persistent, cleared on redeploy |
| Router Cache | RSC payload per segment | browser | session, cleared on refresh |
Request Memoization is a React feature: identical GET fetches in a single render are deduped, so you can fetch the same data in a layout and a page without prop drilling. The other three persist between requests.
fetch is no longer cached by default in Next 15
Wrong "A plain fetch in a server component is cached, so it only hits my API once." That was the Next 14 default (force-cache). Next 15 defaults to auto no cache, and the result is not persisted in the Data Cache.
Right opt in explicitly. { cache: 'force-cache' } stores it in the Data Cache; { next: { revalidate: 3600 } } caches it with a one-hour stale-while-revalidate window; { next: { tags: ['products'] } } makes it revalidatable on demand.
Revalidation: time-based and on-demand
Time-based revalidation (revalidate: n, the ISR pattern) serves cached data inside the window; the first request after serves stale and refreshes in the background. On-demand revalidation runs revalidateTag(tag) or revalidatePath(path) from a Server Action after a mutation, purging the Data Cache and Full Route Cache. Call them in an Action or Route Handler, never during render.
The Next 14 to 15 caching-default flip
Most caching confusion traces to defaults that changed in Next 15:
| Behavior | Next 14 | Next 15 |
|---|---|---|
fetch | cached (force-cache) | not cached (auto no cache) |
| GET Route Handler | cached by default | not cached; opt in with force-static |
| Router Cache pages | 30s dynamic / 5min static | page staleTime: 0; loading.js still 5min |
cookies/headers/params/searchParams | sync | async, must be awaited |
A GET Route Handler sits outside the React tree, so it gets no Request Memoization and, since Next 15, no Data Cache by default. Add export const dynamic = 'force-static' to cache one; metadata handlers (sitemap.ts, opengraph-image.tsx) stay static.
Uncached is not no-store
A default (auto no cache) fetch and an explicit no-store fetch diverge on a static route. The default runs once at next build, bakes into the Full Route Cache, and is not persisted in the Data Cache. no-store is stronger: it opts the route out of the Full Route Cache, so the route turns dynamic and the fetch runs every request. "Uncached" does not imply "runs at request time" unless the route is already dynamic. export const dynamic = 'force-dynamic' also flips every fetch default to no-store.
Which invalidation reaches which cache
revalidatePath/revalidateTagpurge the Data Cache and Full Route Cache; called in a Server Action they also invalidate the client Router Cache.- The same calls in a Route Handler bust the server caches but leave the Router Cache stale, because a handler is not tied to a route; the browser serves the old page until a hard refresh or auto-invalidation.
router.refresh()clears only the Router Cache, then re-renders the route on the server, which still reads unchanged values from the Data Cache.
Both functions throw if called during render in Next 15; they belong in Actions and handlers. The lowest revalidate across a route's layouts and pages sets the window for the whole route.
Suspense does not make a component dynamic
Streaming is on by default: loading.tsx wraps the segment below it in a <Suspense> boundary whose fallback streams first, then the resolved content swaps in. Wrapping a component in <Suspense> does not opt it into dynamic rendering; a component doing only synchronous work still prerenders inside the boundary. Streaming governs when chunks arrive, not whether the route is static. You also cannot fully opt out of the Router Cache: <Link prefetch={false}> disables prefetch, yet a visited route is still cached for the session. The forward-looking model (Next 16 Cache Components with use cache) replaces these implicit defaults with explicit opt-in caching.
beta
The interviewer part is in the works.
The diagnostic, personal maps, and AI mock interviews are being finished right now. The notes stay free either way. Leave an email and you'll get the first-cohort invite, plus a month of Pro when it opens.