Scraping Central is reader-supported. When you buy through links on our site, we may earn an affiliate commission.

2.2intermediate4 min read

Client-Side Rendering vs SSR vs Hybrid

The architectural difference between rendering modes determines whether you scrape with curl, parse a hydration payload, or drive a browser.

What you’ll learn

  • Define CSR, SSR, SSG, ISR, and the modern hybrid model in one sentence each.
  • Identify the rendering mode of a real page from the response and view-source.
  • Map each rendering mode to a scraper strategy.
  • Recognise the framework fingerprints, Next.js, Nuxt, Remix, SvelteKit, Astro.

The single biggest factor in choosing a scraper strategy is where the HTML is generated: on the server, in the browser, or both. Knowing the rendering mode of a page is what separates a thirty-second job from a thirty-minute one.

Five rendering modes in one sentence each

Mode What happens
SSR (Server-Side Rendering) Every request runs through the framework on the server; HTML comes back complete.
CSR (Client-Side Rendering) Server returns a near-empty shell; the browser fetches data and builds the DOM.
SSG (Static Site Generation) HTML is pre-rendered at build time and served as static files; effectively SSR for the scraper.
ISR (Incremental Static Regeneration) SSG, but pages re-render on a schedule or trigger; still looks like SSR from outside.
Hybrid (SSR + hydration) Server renders HTML and embeds a JSON payload; the browser "hydrates" markup into an interactive app.

From a scraper's perspective, SSR, SSG, and ISR all look the same: data is in the HTML. CSR is the hard case. Hybrid is a gift, the data is in the response as JSON, easier to parse than HTML.

How to spot the mode in one minute

Open the page, View Source, and look at the first 200 lines:

  • Massive amount of rendered HTML (product cards, articles, prices) → SSR/SSG/ISR. Scrape with requests.
  • <div id="root"></div> or <div id="__next"></div> with no content inside → CSR. Use a browser or find the API.
  • Rendered HTML plus a giant <script id="__NEXT_DATA__">{...}</script> → Hybrid. Parse the JSON.
  • <script id="serialized-state"> or window.__INITIAL_STATE__ = {...} → also hybrid. Different framework, same idea.

These signatures are not subtle. Three minutes with View Source on any major site teaches you the patterns.

Framework fingerprints

Each major framework leaves an obvious mark:

Framework Telltale Default mode
Next.js <script id="__NEXT_DATA__">, /_next/ static assets SSR/SSG/ISR/Hybrid
Nuxt <script>window.__NUXT__={...}</script> SSR/Hybrid
Remix <script>window.__remixContext = {...}</script> SSR
SvelteKit <script>__sveltekit_*</script> SSR/SSG/Hybrid
Astro data-astro-* attributes, no big JSON SSG (mostly)
Gatsby <script id="___gatsby">, window.pageData SSG
Pure React/Vue SPA <div id="root"> (or #app), no SSR payload CSR

When you see one of these, you immediately know what tool to reach for. A Next.js site? Parse __NEXT_DATA__ and skip the browser. A pure React SPA? Find the JSON API the app calls.

What hybrid actually means

A modern Next.js page does both. The server runs your React code, produces HTML, and embeds the data (props for that page) as JSON in a <script> tag. The browser then "hydrates", wires up event handlers and state without re-fetching.

This is enormous for scrapers. Look at any /products/{slug} page on a Next.js e-commerce site:

<html>
<body>
  <div id="__next">
  <article>
  <h1>Yellow Ceramic Mug</h1>
  <p>$12.00</p>
  <!-- ...full rendered product card... -->
  </article>
  </div>
  <script id="__NEXT_DATA__" type="application/json">
  {"props":{"pageProps":{"product":{"id":1,"name":"Yellow Ceramic Mug","price":1200,"sku":"YCM-001","variants":[...],"description":"..."}}}}
  </script>
</body>
</html>

You could parse the HTML. But the __NEXT_DATA__ payload contains richer data than what's rendered, typed numbers, IDs, variants, internal fields the UI doesn't even display. Always check the hydration payload first.

import json, re, requests
from bs4 import BeautifulSoup

r = requests.get("https://practice.scrapingcentral.com/products/1-white-wooden-vase")
soup = BeautifulSoup(r.text, "lxml")
payload = json.loads(soup.select_one("#__NEXT_DATA__").string)
product = payload["props"]["pageProps"]["product"]
print(product["name"], product["price"], product["sku"])

Three lines, no browser, the full product record.

When the mode determines the tool

If the page is... Scraper strategy
Pure SSR/SSG/ISR requests + BeautifulSoup, or Guzzle + DomCrawler
Hybrid with hydration payload requests + JSON parse, fastest of all
Pure CSR with a discoverable JSON API requests to the API directly (Sub-Path 4)
Pure CSR with no discoverable API Playwright/Selenium
Pure CSR with anti-bot fingerprinting Stealthed Playwright or a SERP API

Browser automation is the last resort, not the default. The art of dynamic-web scraping is recognising when you can avoid it.

Catalog108 examples

  • /products, server-rendered grid. Pure SSR. curl returns everything.
  • /products/1-white-wooden-vase, hybrid Next.js-style with __NEXT_DATA__. Parse the JSON.
  • /challenges/dynamic/spa-pure, pure CSR. View source is empty. Browser required.
  • /challenges/dynamic/spa-routed, pure CSR with client-side routing. Browser required.

Run View Source on each and compare. You will see the four shapes inside ten minutes.

Hands-on lab

Visit /products and confirm via View Source that the grid is server-rendered. Then visit /products/1-white-wooden-vase and find the hydration payload (search the source for __NEXT_DATA__ or application/json). Write a curl | python -c '...' one-liner that extracts the product's SKU from the payload. No browser needed.

Hands-on lab

Practice this lesson on Catalog108, our first-party scraping sandbox.

Open lab target → /products

Quiz, check your understanding

Pass mark is 70%. Pick the best answer; you’ll see the explanation right after.

Client-Side Rendering vs SSR vs Hybrid1 / 8

Which rendering mode produces HTML at build time and serves it as static files?

Score so far: 0 / 0