Next.js rendering: SSR, SSG, ISR and what to choose for SEO
The question that decides whether Google sees your content isn't "which framework to use" — it's "how to render". In React applications, the rendering strategy determines what reaches the initial HTML, how fast the page loads, and whether the crawler sees the text or an empty shell. Choosing wrong is the silent cause of many beautiful sites that simply don't rank.
Next.js offers four rendering models, and most of the confusion comes from treating them as if they were the same thing. Let's separate each one, show the SEO impact, and give a practical guide on when to use which — focused on the App Router, today's default.
The four rendering models
CSR — Client-Side Rendering
In CSR, the server sends nearly empty HTML and JavaScript assembles the whole page in the browser. It's the model of a "pure" React app (Create React App, a Vite SPA).
For SEO, it's the worst case. The initial HTML has no content; Google has to download the bundle, run it, and wait for rendering to see anything. It does that, but with a queue, a delay, and no guarantee. Content that depends on a client fetch may never get indexed. Use CSR only for screens behind login, dashboards, and areas that don't need to rank.
SSR — Server-Side Rendering
In SSR, the server renders the full HTML on each request and sends it ready. The user (and the crawler) get the content in the first byte; JavaScript then "hydrates" the page to make it interactive.
For SEO, it's excellent: the content is in the initial HTML, always up to date. The cost is performance and infrastructure — rendering on every request consumes server CPU and adds latency (TTFB) if rendering is heavy or the data is slow. Use SSR when content is dynamic and must always be fresh: search results, per-user personalized pages, data that changes by the minute.
SSG — Static Site Generation
In SSG, pages are rendered once, at build time, and served as static HTML. It's the fastest possible model: the server (or CDN) just delivers a ready file, with minimal TTFB.
For SEO, it's ideal when the content doesn't change per request: marketing pages, landing pages, blog articles, documentation. Content in the HTML, maximum speed, near-zero server cost. The limitation is freshness — to update, you have to rebuild. It's exactly this blog's model: each article is generated at build and served statically.
ISR — Incremental Static Regeneration
ISR is the middle ground between SSG and SSR: pages are static, but Next regenerates them in the background after an interval (or on demand). The first visitor after expiration gets the old version; in parallel, a new one is generated and starts being served.
For SEO, it combines the best of both worlds: static speed with periodic freshness, no manual rebuild. Use ISR for e-commerce catalogs, listings that change a few times a day, or any content where "fresh every hour" is enough.
The App Router model
In the Next.js App Router, the choice is no longer "export a special function per page" — it's the composition of Server and Client Components, plus the data caching strategy.
Server Components are the default. They run only on the server, ship no JavaScript to the client, and can fetch data directly. Any content that doesn't need interactivity should be a Server Component — that's what puts the text in the initial HTML for free, with no weight on the bundle.
Client Components ('use client') are the exception. Use them only where there's state, events, or browser APIs: a form, a menu that opens, a carousel. They add JavaScript and hydration cost, so keep them small and at the leaves of the tree.
Data caching defines the effective model. A Server Component that fetches cacheable data and runs at build becomes SSG. If it uses a request-time API (cookies, headers) or uncached data, it becomes dynamic (SSR). With time-based revalidation, it becomes ISR. In other words: you don't "turn on SSG" — you write the component, and Next decides the model based on how the data is consumed.
Streaming with Suspense. The App Router can send HTML in chunks: ready content arrives first, and the slow parts (behind <Suspense>) arrive later. This improves time to first byte and LCP without sacrificing indexable content.
How to choose: a practical guide
The decision fits in a few questions:
- Does the page need to rank? If not (dashboard, logged-in area), CSR works and simplifies things.
- Is the content the same for all users? If yes, it's a candidate for static (SSG/ISR).
- How often does it change? Never/rarely → SSG. A few times a day → ISR. On every request → SSR.
- Does it depend on user or request data? (cookies, geolocation, session) → SSR/dynamic.
In practice, a well-structured product mixes everything: landing and blog in SSG, catalog in ISR, search and user account in SSR, and the internal panel in CSR. The mistake is applying a single model to everything — usually CSR, out of SPA inertia, precisely on the pages that would most need to rank.
When content is dynamic but needs to rank
The hardest case — and the most common in e-commerce and marketplaces — is content that changes often and needs to show up on Google: catalogs, listings, category pages. Here the rendering choice mixes with architecture decisions:
- Catalogs and categories → ISR. Product and category pages rarely change every second, but they need to stay fresh. Generating them statically with revalidation (time-based, or on-demand when the product changes) delivers static speed with freshness — the best scenario for ranking large listings.
- Pagination → each page indexable and self-sufficient.
/category?page=2should have its owncanonical(pointing to itself, not to page 1), unique content, and links to neighboring pages. Treating pagination as a duplicate of page 1 is a mistake that hides half the catalog from Google. - Filters and facets → control the URL explosion. Combinations like
?color=blue&size=m&sort=pricegenerate nearly infinite URLs that burn crawl budget and fill the index with thin pages. Decide which facets have search value (those can be indexable and static) and which don't (those get acanonicalto the clean version, ornoindex). - Internal search → usually
noindex. Search result pages (?q=...) tend to be thin, infinite content; the default is to keep them out of the index and invest in well-structured category pages instead.
The rule that ties it together: statically render (ISR) what has search value and is stable enough, and keep out of the index what's infinite or thin. Rendering solves speed; URL architecture solves what deserves to exist for Google.
Common mistakes that cost indexing
- Rendering critical content client-only. Text that appears after a
useEffector a browser fetch isn't reliable for Google. If the page needs to rank, the content comes from the server. - Generating metadata on the client. Title, description, and structured data need to be in the initial HTML. Use the framework's metadata API, not
document.titleafter hydration. - Overusing
'use client'. Marking a whole big component as client drags everything it imports into the bundle and hydration, worsening INP. Isolate the interactivity. - Forcing everything dynamic by accident. Reading
cookies()orheaders()in the wrong place opts the whole route into dynamic, losing the static benefit. Know what makes a route dynamic. - Trusting that "Google renders JS". It does render, but with delay and limits. Relying on that is betting your traffic on a queue you don't control.
Caching, revalidation, and content freshness
In the App Router, "static vs dynamic" is, in practice, a matter of data caching. Understanding the three levers avoids the biggest source of surprises:
- Cached
fetch(default). By default, Next can cache afetchresult and make the route static. That's the SSG path. - Time-based revalidation (
revalidate). You say "this data is valid for N seconds"; after that, the next visit triggers a background regeneration. That's ISR — static with periodic freshness. - On-demand revalidation. Instead of waiting for the timer, you invalidate a page's cache (or a tag) when the content actually changes — for example, when a post is published in the CMS. It's the best of both worlds: static, but always correct.
// Regenerate at most once an hour (ISR)
export const revalidate = 3600
export default async function Page() {
const data = await fetch('https://api.example.com/catalog').then((r) => r.json())
return <Catalog data={data} />
}
The most common trap is making a route dynamic by accident: just using cookies(), headers(), or searchParams in the wrong place makes Next give up on static and render on every request. That's not a bug — it's the expected behavior — but it catches many people off guard when the site "got slow after a small change". Know exactly what opts your route into dynamic.
How to migrate a page from CSR to SSR
If you have an important page rendering client-only, the migration usually follows this path:
- Identify what needs interactivity and what's just presentation. Only the first group needs to be a Client Component.
- Move data fetching to the server. Instead of a
useEffect+fetchon the client, fetch the data directly in the Server Component (which can beasync). - Render the content on the server and pass only what's needed to the Client Components at the leaves.
- Confirm in the source (Ctrl+U) that the content is now in the initial HTML.
The gain is twofold: Google starts seeing the content immediately, and LCP improves because the user doesn't wait on the bundle. In products where we did this transition, it's common to see pages that didn't index start ranking within weeks — not because the content changed, but because the crawler could finally read it.
How to verify what Google sees
Don't trust how it looks in the browser — trust the HTML.
- Ctrl+U (view source) shows the initial HTML, exactly what arrives before JavaScript. Look for your main text and links there.
- Search Console → URL Inspection → Rendered HTML shows what Googlebot sees after rendering.
- Turn off JavaScript in DevTools and reload. If the page goes blank, the crawler has the same problem.
If the content is in the source, you're on the right track. If it only appears with JS on, it's time to move that page to SSR or SSG.
Conclusion
Rendering isn't an implementation detail — it's the decision that determines whether your content exists for Google. CSR for what doesn't need to rank; SSG for stable content (and it's the fastest); ISR for content that changes a few times a day; SSR for what's dynamic and personalized. In the App Router, that translates to Server Components by default, Client Components only at the leaves, and a deliberate caching strategy. Choose per page, not for the whole site.
Ready to respawn?
If you're not sure Google is seeing your content, that's exactly the kind of thing an audit reveals in minutes. Request a free audit — within 7 days you'll get a diagnosis prioritized by impact and effort, no strings attached.