Guide · SPA, SSR and hydration
Render SPA to HTML: what each rendering mode sends first
CSR, SSR, SSG, streaming and islands from the reader's side: what lands in the first HTTP response and where Next.js, Nuxt and Astro keep the data.
Guide
This article was drafted with AI assistance from the sources listed below and edited and published by Teneo Labs.
| Criterion | Text in raw HTML? | Data as JSON? |
|---|---|---|
| Client-side rendering (CSR) | No, only a mount point | Sometimes, often fetched later |
| Server-side rendering (SSR) | Yes | Usually, for hydration |
| Static generation (SSG) | Yes | Often, for hydration |
| Streaming SSR | Yes, in chunks | Yes, inline scripts |
| Islands (e.g. Astro) | Yes, except inside islands | Props on island elements |
"Render SPA to HTML" is asked from two sides. Site owners want their single-page app to send real HTML. Readers, the people fetching pages for pipelines and LLMs, want to know what they will receive. Framework docs explain rendering modes from the builder's side. This guide explains them from the reader's side: what each mode puts in the first HTTP response, and where the data hides when the text does not.
The one question that matters
For a reader there is only one question: is the text in the first response? Everything else, including the framework name, is a hint about where to look.
curl -sL https://example.com/ | grep -oiE '<p[ >]' | wc -l
A high count means server-rendered text. A count near zero means either client rendering or a page made of something other than paragraphs. The next sections tell you which.
Client-side rendering (CSR)
A classic SPA, for example a Vite or Create React App build, sends an HTML file with a mount point and script tags. The browser downloads the bundle, calls APIs and builds the DOM. The first response has no content.
What you can do as a reader:
- Check whether the site offers a documented public API or feed, and use it within the site's terms.
- Check for JSON-LD or
og:tags in the head, which some SPAs still render on the server. - Otherwise, render it in a headless browser; the headless browser guide covers wait logic and cost.
Whatever you choose, check robots.txt and the site's terms of service first, and stay within the product's acceptable use policy if you use Live Web Data API.
Server-side rendering (SSR)
The server runs the components and sends finished HTML for each request. The text is in the first response. The page then hydrates: the framework attaches event handlers in the browser, using a JSON copy of the data that the server embedded in the page.
That JSON is useful. It holds the same content as the visible text, structured, and you can parse it without running a script.
Static generation (SSG) and prerendering
The same as SSR, but done at build time. The HTML is a file on a CDN. For readers it is the easiest case: text in the first response, often with hydration data alongside. Prerendering an existing SPA, for example with a framework's static export, turns a CSR page into this case.
Streaming SSR and React Server Components
Streaming sends HTML in chunks as the server finishes each part. The text still arrives in the first response, but later chunks may sit at the end of the document inside inline <script> tags that move them into place. A fetcher that reads the whole response gets everything. One that stops at a size limit may miss the tail.
In the Next.js App Router, the React Server Components payload appears as calls to self.__next_f.push(...) in inline scripts. It is not plain JSON, but it contains the rendered text and props, which is why simple text searches often find content there.
Islands
Astro and similar tools render the page to static HTML and only hydrate interactive parts, the islands. Text outside islands is plain HTML. An island that renders only on the client (client:only in Astro) is empty in the first response, and its props are serialised as attributes on the <astro-island> element.
Where each framework keeps the data
| Framework | What to search for in the raw HTML |
|---|---|
| Next.js, Pages Router | <script id="__NEXT_DATA__" type="application/json"> with page props |
| Next.js, App Router | self.__next_f.push( inline scripts (RSC payload) |
| Nuxt 3 | <script id="__NUXT_DATA__" type="application/json"> |
| SvelteKit | data from load functions serialised into an inline script at the end of the body |
| Remix / React Router | a context object assigned in an inline script |
| Astro | <astro-island> elements with a props attribute |
These are implementation details and change between versions. Treat them as places to look, not as stable APIs, and re-check after a site redeploys.
A quick classifier
URL=https://example.com/
HTML=$(curl -sL "$URL")
echo "$HTML" | grep -oiE '<p[ >]' | wc -l # server-rendered text?
echo "$HTML" | grep -oE '__NEXT_DATA__|self\.__next_f|__NUXT_DATA__|astro-island' | sort -u
echo "$HTML" | grep -oE '<div id="(root|app)"></div>' # empty mount point?
Many paragraphs: SSR or SSG, read it directly. Few paragraphs plus a framework payload: parse the payload. Few paragraphs, a mount point and no payload: CSR, so a headless browser is needed, or a documented feed or API if the site publishes one. The headless browser test walks through the full decision.
For site owners
If your own app falls in the CSR row and you want fetch tools and AI crawlers to read it, move the main text to SSR, SSG or prerendering. Google calls dynamic rendering, serving different HTML to bots, a workaround rather than a long-term fix. The AI crawler guide covers what those crawlers receive today.
Test it yourself
Run the classifier on one page each from a docs site, a news site and a web app you use. Note the mode you think each one uses and whether the text was in the first response. If you find a framework payload, open it in a JSON viewer and check that it matches the visible text.
Frequently asked questions
What does it mean to render an SPA to HTML?
It means producing the page's HTML on a server or at build time instead of in the visitor's browser. For site owners that is SSR or prerendering. For readers it means either a server that already does this, or a headless browser that does it for you.
Is SSR better than an SPA for scraping?
For reading text, yes: an SSR page sends its text in the first response, so a plain fetch can read it. An SPA that renders only in the browser needs either its embedded data or a headless browser.
What is hydration data?
The JSON a server-rendered page includes so the client-side framework can take over without fetching again. It usually holds the same content as the visible page, in structured form.
Can I prerender a single-page app without changing frameworks?
Usually. Most SPA frameworks have a static generation or prerender option, and build-time prerender tools can snapshot routes. Pages that depend on per-request data still need SSR.
Sources
- Rendering on the Web (web.dev) (Documentation)
- Server and Client Components (Next.js docs) (Documentation)
- Islands architecture (Astro docs) (Documentation)
- useNuxtApp and payload (Nuxt docs) (Documentation)