SSR, SSG, ISR or CSR: choosing a rendering strategy per route
Rendering strategy is a per-route decision, not an architectural religion. A single Next.js app can and should use all four.
Next.js has four distinct caches: request memoisation (per render), the data cache (persistent, server-side), the full route cache (rendered HTML and RSC payload), and the client router cache (in-memory, per session). Stale content is almost always one specific layer, and each is invalidated differently — revalidateTag and revalidatePath for the server caches, navigation and refresh for the client one.
They are request memoisation, the data cache, the full route cache and the client router cache — ordered from shortest to longest lived.
| Cache | Scope | Lifetime | Cleared by |
|---|---|---|---|
| Request memoisation | One server render | Single request | Automatic |
| Data cache | Server, across requests and deploys | Until revalidated | revalidateTag / revalidateAge / revalidatePath |
| Full route cache | Server, per route | Until revalidated or redeployed | revalidatePath / new deploy |
| Router cache | Browser, per session | Seconds to minutes | router.refresh(), hard navigation |
They stack. A page can be served from the full route cache, whose HTML was produced using values from the data cache, which were themselves fetched once thanks to request memoisation — and the browser may not even ask the server because of the router cache.
Opt out explicitly at the level you mean. Blanket opt-outs at the top of every file are the reason many Next.js apps perform no better than a plain server-rendered app.
// Route segment: always render on request
export const dynamic = "force-dynamic";
// Route segment: rebuild at most every 60 seconds
export const revalidate = 60;
// Per fetch: never cache this one call
const res = await fetch(url, { cache: "no-store" });
// Per fetch: cache and tag it for targeted invalidation
const res = await fetch(url, { next: { revalidate: 300, tags: ["posts"] } });Use revalidateTag when you know which data changed; use revalidatePath when you know which page changed.
Tags map to data. If an editor updates a blog post, revalidateTag('posts') invalidates every cached fetch tagged 'posts' — the listing page, the sitemap, the related-posts block — without you enumerating routes. That is exactly what you want in a content system with many pages built from the same source.
Paths map to routes. revalidatePath('/pricing') is right when a single known page changed and you do not want to think about tags. It is blunter: it discards the rendered output for that route regardless of which data changed.
"use server";
import { revalidateTag } from "next/cache";
export async function publishPost(input: PostInput) {
await db.post.create({ data: input });
revalidateTag("posts"); // every page built from tagged post data refreshes
}Because the client router cache never heard about it. Next.js keeps the RSC payload of visited routes in memory so back-and-forward navigation feels instant. A server-side revalidation does not reach into that in-memory store.
The direction of travel is explicit caching. Earlier versions cached fetch requests by default, which surprised people; current versions default to no caching for fetch and ask you to opt in. Next.js 16 continues this with an explicit 'use cache' directive that marks a function or component as cacheable, plus cache lifetime helpers, so caching becomes something you declare rather than something you discover.
The practical advice is version-independent: know which layer you are relying on, tag your data, and write an integration test that asserts a published change appears within your intended revalidation window.
Development disables most caching. In production the full route cache and data cache are active, so a page that re-rendered on every local request may be served from cache for minutes. Reproduce with a production build locally before debugging further.
Yes, expressed through the revalidate segment option or per-fetch revalidate values. The behaviour — serve cached, rebuild in the background after the window — is the same idea as Incremental Static Regeneration in the Pages Router.
A deployment invalidates the full route cache because the build output changes. The data cache persistence depends on your hosting platform; on most managed platforms it is also reset per deployment.
Harshal Patel
Founder & Lead Engineer, ROVQIX
Harshal leads engineering at ROVQIX, where he has shipped production Next.js, Node.js and PostgreSQL systems for startups, SaaS teams and ecommerce brands. He writes about the trade-offs behind architecture decisions rather than the framework of the week.
ROVQIXdesigns and builds production web platforms — Next.js front ends, Node.js APIs and the infrastructure behind them. Tell us what you're building and we'll scope it with you.
Rendering strategy is a per-route decision, not an architectural religion. A single Next.js app can and should use all four.
Caching is easy until the cache expires. Everything interesting about Redis in production happens in the seconds after a popular key disappears.
Launch day problems are almost never novel. This is the list that catches them — the same one we run on every client deployment.
No spam. Just the occasional case study and craft breakdown.