# Next.js Caching Strategies: SSG vs ISR vs SSR — How to Choose

> A clear comparison of Next.js caching strategies — SSG, ISR, and SSR — with decision criteria for each and how the App Router's cache layers change the picture.

- **Published:** 2026-01-07
- **Author:** Clixo
- **Reading time:** 6 min read
- **Tags:** next.js, caching, isr, ssr, performance
- **Canonical URL:** https://clixo.sh/blog/nextjs-caching-strategies-isr-ssr-ssg

Choosing the wrong rendering strategy in Next.js costs you either performance or correctness. A marketing page served dynamically on every request wastes server compute. A product inventory page cached for an hour shows stale stock counts. Neither failure is catastrophic until it is — when traffic spikes or a customer buys a product that's out of stock.

The good news is that the decision framework is not that complicated. Understanding what each strategy actually does makes the choice obvious for most routes.

## The Three Core Strategies

### SSG: Static Site Generation

Next.js renders the page at build time and stores the HTML on a CDN. Every visitor gets the same pre-built file. There is no server compute per request, no database query at runtime, and Time to First Byte is as low as it gets — typically under 50ms from a CDN edge.

The tradeoff: the data is frozen at build time. Any change requires a rebuild and redeploy. For content that genuinely does not change between deploys (documentation, legal pages, a company's about page), SSG is the correct default.

### ISR: Incremental Static Regeneration

ISR is SSG with a freshness window. The page is pre-built, but when the cached version becomes stale (based on a `revalidate` interval you set), the next request is served from cache while Next.js regenerates the page in the background. Subsequent requests get the updated version.

The key property of ISR is that **no user waits for a rebuild**. The stale page is always served immediately; regeneration happens asynchronously. This is sometimes called the stale-while-revalidate pattern.

ISR also supports on-demand revalidation. Instead of waiting for a time interval, you call `revalidateTag` or `revalidatePath` from a Server Action — for example, after a content editor publishes a change. The cache is purged immediately and the next request triggers a fresh build.

### SSR: Server-Side Rendering

Next.js renders the page on the server for every request. The database is queried, the HTML is built, and the response is sent — all on demand. The data is always fresh.

The cost is server compute and latency on every request. SSR is appropriate when the page must reflect data that changes between individual requests and cannot be shared across users — a logged-in user's dashboard, a real-time inventory count, or a page that reads from the request's cookies or headers.

In the App Router, SSR is triggered by exporting `dynamic = 'force-dynamic'` from a page, or by reading a dynamic value like `cookies()` or `headers()` inside the component.

## How the App Router Changes the Picture

The App Router adds a component-level cache that sits above these page-level strategies. Individual fetch calls within a server component can have their own cache configuration, independent of the page's overall rendering mode.

This means a page can be statically rendered overall, but include a section that fetches fresh data on every request. Or a dynamic page can cache some of its data fetches while leaving others fresh.

The practical result: you no longer have to choose one strategy per page. You choose one strategy per data source, and the page's rendering mode reflects the most dynamic dependency it has.

## The `use cache` Directive

Recent Next.js versions introduced the `use cache` directive, which lets you cache the return value of any async function — not just `fetch` calls. This is how you bring ISR-style caching to database queries, third-party SDK calls, or any async operation.

```typescript
async function getProductData(id: string) {
  'use cache'
  // next.revalidate(3600) — cache for 1 hour
  return db.products.findUnique({ where: { id } })
}
```

This is more flexible than configuring caching at the page level. You can have a single route with a mix of cached and uncached data sources, each with its own freshness window.

## How to Choose: A Decision Framework

```mermaid
flowchart TD
  A["New Route"] --> B{"Same content for all users?"}
  B -->|Yes| C{"Changes only at deploy time?"}
  C -->|Yes| D["SSG — static, CDN-cached"]
  C -->|No| E{"Tolerate brief staleness?"}
  E -->|Yes| F["ISR — stale-while-revalidate"]
  E -->|No| G["SSR — dynamic per request"]
  B -->|No| H{"Personalized per user?"}
  H -->|Yes| G
  H -->|No| F
```

**Use SSG when:**
- The content is the same for every visitor
- The content changes only when the codebase changes (documentation, marketing pages)
- You want maximum CDN performance with zero server compute

**Use ISR when:**
- The content is shared across visitors but changes periodically (blog posts, product pages, news articles)
- You can tolerate a brief window of stale data (seconds to minutes)
- On-demand revalidation after CMS publishes covers your real-time needs

**Use SSR when:**
- The page is personalized per user (account pages, dashboards)
- The page reads from request cookies or headers (auth-gated content)
- The data changes so frequently that any staleness is unacceptable (live pricing, real-time stock)

## Mixing Strategies Within a Route

A product detail page is a useful example. The product name, description, and images change rarely — ISR with a long revalidation window is appropriate. The available stock count changes frequently — it should either be fetched dynamically or pulled client-side from a lightweight API endpoint. The recommended products section is personalized — it needs to be dynamic.

In the App Router, you can build exactly this:

- The page-level component uses ISR for the static product data
- A `Suspense`-wrapped component fetches stock count dynamically
- A client component fetches personalized recommendations client-side after hydration

No single strategy label applies to the whole page. The architecture reflects the actual data requirements.

## Common Misconceptions

**ISR is not real-time.** There is always a window of staleness. If your use case demands sub-second data freshness, you need SSR or a client-side fetch.

**SSR does not mean slow.** A well-optimized SSR page with a fast database query and a CDN in front of the edge runtime can feel as fast as a static page. The latency comes from the query, not from the pattern itself.

**Disabling caching everywhere is not a strategy.** Some teams add `cache: 'no-store'` to every fetch out of caution. This abandons all performance benefits and increases server load. Understand what each cache layer does and opt out only where you have a reason.

---

Getting caching right in Next.js means your application is both fast and correct — not one or the other. The App Router gives you the tools to achieve both; the discipline is in using them deliberately.

If you're building a Next.js application and want engineering help that treats caching as a first-class architectural concern, [start a build with Clixo](https://clixo.sh/#contact).

---

Clixo · 1141 W Bryn Mawr Ave, Itasca, IL 60143, US · [hello@clixo.sh](mailto:hello@clixo.sh)
[Start a build](https://clixo.sh/#contact) · [All services](https://clixo.sh/services) · [Agent guide (llms.txt)](https://clixo.sh/llms.txt)
