It is 3am. Your Lighthouse score is 34. Your manager Slacked you a screenshot of the homepage taking eleven seconds to load on his phone — which, to be fair, is a 2019 Android device running Chrome with forty-seven open tabs, but that is beside the point. You are now debugging your rendering strategy.
You open the Next.js docs. You see SSR. SSG. ISR. CSR. PPR. You see use cache, cacheLife, cacheTag. Somewhere in the back of your mind, a meme plays: "I just wanted to render a div."
Welcome to The Rendering Menu — a restaurant where the food is HTML, the chefs are servers (literally), and every time you think you have picked the right dish, the framework changes the entire menu. Next.js 16 just did exactly that. Let us figure out what to order.
The Plot Twist: Everything Is Dynamic Now
If you are coming from Next.js 14 or 15, brace yourself. The biggest philosophical shift in Next.js 16 is this: all pages are dynamic by default. No caching. No static generation. Every request hits the server fresh, like a customer walking into a restaurant and demanding their steak be cooked from scratch.
Remember export const dynamic = 'force-dynamic'? Gone. Remember cache: 'no-store' on fetch? Unnecessary. The old implicit caching behavior that made your pages magically fast (and also magically stale at the worst possible moment) has been evicted. You now opt into caching with the new "use cache" directive. Which, yes, is a string literal at the top of a function. The React team really said "we liked 'use client' so much, let us make directives a genre."
To unlock the full rendering menu, you flip one switch in your config:
unknown nodeOne flag. cacheComponents: true. It replaces the old experimental.ppr and experimental.dynamicIO flags. The Next.js team merged two experimental features into one stable toggle, which is the framework equivalent of combining shampoo and conditioner. Controversial, but efficient.
SSR: The Default (You Are Already Using It)
Server-Side Rendering in Next.js 16 is what happens when you do nothing. Write a Server Component. Fetch some data. Return some JSX. Congratulations, you have SSR. The server renders fresh HTML on every request, streams it to the client, and moves on with its life.
unknown nodeNo magic. No gotchas. The page is rendered on the server, sent as HTML, and hydrated on the client. If your data changes every second (stock tickers, live dashboards, that one analytics page your CEO refreshes obsessively), SSR is your friend.
One important Next.js 16 quirk: params and searchParams are now Promises. Yes, really. You must await them:
If you forget the await, TypeScript will not save you and the error message will make you question your career choices. Ask me how I know.
When to use SSR:
- Personalized dashboards (user-specific data)
- Real-time data that must be fresh on every load
- Pages that read cookies, headers, or searchParams
- Auth-gated content where stale data is a liability
SSG: The Speed Demon (When Your Content Sits Still)
Static Site Generation is the art of doing all the work once and then never doing it again. The HTML is generated at build time, uploaded to a CDN, and served to every user faster than they can blink. It is the drive-through of rendering strategies — pre-made, wrapped, and ready.
In Next.js 16, any Server Component that does not access network resources, runtime APIs, or async data is automatically static. You do not need to export anything. You do not need to configure anything. The framework looks at your component and says, "This is boring. I can prerender it."
unknown nodeFor dynamic routes (like blog posts), you use generateStaticParams to tell Next.js which paths to pre-build:
With cacheComponents: true, you can also force dynamic data into the static shell using "use cache":
When to use SSG:
- Marketing and landing pages
- Blog posts and documentation
- Product catalogs that change infrequently
- Any page that is identical for every user (the universal truth pages)
ISR: The Best of Both Worlds (Or: Schrödinger's Cache)
Incremental Static Regeneration was always the weird middle child. "I want the speed of static, but I also want my data to update sometimes." It is like wanting a frozen pizza that also tastes freshly baked. And somehow, it works.
In Next.js 16, ISR has evolved. The old export const revalidate = 60 still works if you are not using Cache Components, but the new hotness is "use cache" with cacheLife:
The cacheLife function accepts built-in profiles or custom objects. Here is the menu (see, I told you this was a restaurant):
- "default" — stale after 5 min, revalidates at 15 min
- "hours" — revalidates every hour
- "days" — revalidates daily
- "weeks" — revalidates weekly
- "max" — cached for as long as humanly possible
Or you can go full control freak with a custom object:
unknown nodeThe killer feature is on-demand revalidation. Instead of waiting for the timer to expire, you can invalidate specific cached entries from a Server Action. Imagine your CMS editor publishes a new blog post and the page updates instantly without a full rebuild:
unknown nodeNext.js 16 also introduced updateTag for immediate invalidation (read-your-writes consistency) versus revalidateTag which uses stale-while-revalidate (eventual consistency). The difference? revalidateTag serves the old page while regenerating. updateTag blocks until the new page is ready. Choose your fighter.
When to use ISR:
- Blog posts with periodic updates
- E-commerce product pages with price changes
- News feeds that can tolerate slight staleness
- CMS-driven content with webhook-based revalidation
CSR: The Client-Side Cowboy
Ah, Client-Side Rendering. The OG. The useState and useEffect power couple. The rendering strategy that says, "The server? Never heard of her. I do everything in the browser."
In Next.js 16, CSR is what happens when you slap "use client" at the top of a component. The component still gets server-rendered for the initial HTML (so your SEO is not completely doomed), but all the interactive logic — state, effects, event handlers — runs in the browser after hydration.
Here is our actual header component as an example of CSR done right. Notice how it manages scroll state, mobile menu toggles, and hydration awareness all on the client:
unknown nodeNotice the hydration pattern: we pass hasSession from the server as a prop, use it for the initial render, then switch to the live session data after hydration. This prevents the dreaded flash where the "Login" button appears for a split second before switching to the user avatar. The Hydration Blink of Shame, as I affectionately call it.
The golden rule of CSR in Next.js 16: push the client boundary as low as possible. Do not mark your entire layout as a Client Component. That is like putting the entire restaurant in a to-go box. Keep the shell on the server and only wrap the interactive leaves:
unknown nodeWhen to use CSR:
- Interactive UI: forms, modals, dropdowns, accordions
- Browser APIs: window, localStorage, navigator
- State management: useState, useReducer, useContext
- Event handlers: onClick, onChange, onSubmit
- Third-party libraries that demand the browser (analytics, maps, rich text editors)
PPR: The Plot Twist Nobody Saw Coming
Alright, here is where Next.js 16 gets genuinely interesting. Partial Prerendering — PPR — is the headline feature, and it solves a problem that has haunted frontend developers for years: what if a page is both static AND dynamic?
Think about a typical e-commerce product page. The product name, description, and images? Static — they barely change. The price? Maybe ISR. The user's cart icon showing 3 items? Dynamic — that is per-user, per-session. In the old world, you had to pick one rendering strategy for the whole route. PPR says: why not all of them?
Here is how it works. The static parts of your page are prerendered into an instant shell. Dynamic parts are wrapped in <Suspense> boundaries with fallbacks. The shell ships immediately, and the dynamic holes stream in as the data resolves:
The result? The user sees the page skeleton in milliseconds. The product details fill in from cache almost instantly. The cart streams in as the server reads cookies and queries the database. Three rendering strategies, one route, zero compromises. It is like a restaurant that serves your appetizer before you even order the main course.
One important gotcha: if a dynamic component is not wrapped in <Suspense> and is not marked with "use cache", Next.js 16 will throw an error: Uncached data was accessed outside of <Suspense>. This is intentional. The framework is forcing you to be explicit about your rendering boundaries. It is like a waiter refusing to take your order until you actually look at the menu. Annoying? Slightly. Correct? Absolutely.
The Decision Matrix (What to Order)
Here is the cheat sheet. Clip it. Bookmark it. Tattoo it on your forearm. I will not judge.
- Static content, same for everyone → SSG (automatic, or "use cache" + cacheLife("max"))
- Content that updates periodically → ISR ("use cache" + cacheLife("hours" / "days"))
- Personalized or real-time data → SSR (default, no config needed)
- Interactive UI, browser APIs, state → CSR ("use client" directive)
- Page with both static and dynamic parts → PPR (Suspense boundaries + "use cache")
Migrating From Old Next.js (The Rosetta Stone)
If you are upgrading from Next.js 14 or 15, here is your translation guide. Think of it as the Duolingo of rendering strategies, minus the passive-aggressive owl:
- export const dynamic = 'force-dynamic' → Delete it. Dynamic is the default now.
- export const dynamic = 'force-static' → Replace with "use cache" + cacheLife("max")
- export const revalidate = 60 → Replace with "use cache" + cacheLife({ revalidate: 60 })
- cache: 'no-store' on fetch → Delete it. No caching is the default.
- next: { revalidate: 60 } on fetch → Move to component-level "use cache" + cacheLife
Real-World Pattern: The SaaS Dashboard
Let us put it all together with a real example. A SaaS dashboard page that combines all four strategies — because in production, you never use just one:
unknown nodeFour rendering strategies. One page. Each component renders exactly the way it should. The static banner ships instantly. The global metrics come from cache. The user analytics stream from the server. The interactive charts hydrate on the client. It is a rendering orchestra, and PPR is the conductor.
The Bottom Line
Next.js 16 did something radical: it made the simple thing the default thing. Dynamic rendering requires zero configuration. Caching is opt-in and explicit. Partial Prerendering lets you stop thinking in terms of per-route strategies and start thinking per-component. The rendering menu went from "pick one" to "mix and match."
The framework finally acknowledged what we all knew: real pages are not purely static or purely dynamic. They are a messy, beautiful mix of both. And now the tooling matches the reality.
So go forth. Enable cacheComponents: true. Wrap your dynamic content in Suspense. Cache what deserves caching. Render what needs rendering. And the next time someone asks you "SSR or SSG?", smile and say: "Yes."
Now if you will excuse me, it is 4am and my Lighthouse score still says 34. Turns out the real performance bottleneck was the 4MB hero image I forgot to optimize. Some problems, no rendering strategy can fix.

