← Back to Blog

The SPA audit gap, solved by paste — no serverless Chromium required

The SPA audit gap, solved by paste — no serverless Chromium required

Every audit tool I've built fetches HTML from a URL and parses it. That works for 70% of the web. It fails for React / Vue / Svelte / Angular apps that inject their content after JavaScript hydration. To those audits, a React SPA looks like an empty <div id="root"></div>.

The "right" fix is a serverless function running headless Chromium that renders the page, waits for JS, and returns the hydrated HTML. That works. It costs $30-100/month for the infrastructure. I don't want to build that.

The Rendered DOM Paste Audit is the pragmatic alternative: you paste the outerHTML from DevTools, the tool audits the post-hydration DOM, and optionally diffs against the server HTML.

What it does

  1. Optional: Enter the URL.
  2. Required: Paste the rendered <html> outerHTML (from Chrome DevTools > Elements > right-click <html> > Copy > Copy outerHTML).
  3. Click Run.
  4. Tool audits the rendered DOM for: title, meta description, H1 count + content, canonical, lang attribute, JSON-LD block count, images + alt coverage, main-content word count, internal link count.
  5. If you supplied the URL, the tool also fetches the server HTML, runs the same audits, and shows a diff table highlighting checks where rendered DOM differs from static HTML. Those items are what Googlebot first-crawl misses.

Why paste and not automate?

The paste is the trade-off. Automating means running a headless browser. That means one of:

  • Serverless Chromium (Puppeteer + @sparticuz/chromium on Lambda / Netlify) — ~$30/mo at low volume, more at scale, requires a ~50MB bundle
  • Browserless.io — hosted headless Chrome service, $0-99/mo depending on tier
  • Prerender.io — $15-115/mo depending on page count
  • Rendertron — self-hosted, requires a VM
  • Your own CI — GitHub Actions running Playwright daily, free on public repos

The paste is free, immediate, and accurate because you're using YOUR actual browser to render — same engine Googlebot uses, same network conditions, same extensions disabled if you test in incognito.

The trade-off is: you can't automate. But for one-off SPA audits, paste is fine. For recurring monitoring, you'd need one of the automation paths above.

How to capture rendered HTML correctly

  1. Open Chrome (or any Chromium browser).
  2. Navigate to your SPA URL.
  3. Wait for the full UI to render. If it's a dashboard, scroll around. If it's a marketing page, wait for all lazy-loaded content. Googlebot waits ~5 seconds; match that.
  4. Open DevTools (F12 or Cmd+Opt+I).
  5. In the Elements panel, find the <html> tag at the top.
  6. Right-click it. Select Copy > Copy outerHTML.
  7. Paste into the tool.

If your SPA hydrates multiple states (e.g. a logged-out homepage that shifts to a logged-in state), capture the state you want Googlebot to see. Usually that's logged-out.

The diff output is the payoff

The interesting part isn't the rendered audit — it's the diff. When you supply the URL, the tool fetches what the server sent and audits that too. Then it shows you checks where the results differ.

Example output for a React SPA:

Check Static HTML Rendered DOM
<title> fail: (missing) pass: "Our Product Page"
meta description fail: (missing) pass: "Our product does X..."
H1 count fail: 0 H1s pass: 1 H1: "Welcome"
Word count fail: 0 words pass: 1,340 words

Every row in that diff is content Googlebot's first crawl doesn't see. Google's two-stage indexing DOES eventually get the rendered content, but the first pass is what sets initial rank. If every check fails on the first pass, you're starting indexing cold.

The fix is SSR (server-side rendering). Next.js, Nuxt, SvelteKit, Astro — all offer SSR modes. The Code-Diff Patch Generator can't fix SPA architecture for you, but this tool helps you see why you need to.

What to do after running it

If the diff shows mostly passes on both sides → your SSR is working. Nothing to fix here.

If the diff shows static fails + rendered passes → enable SSR in your framework. Priority:

  1. Next.js — use getServerSideProps or (App Router) export const dynamic = 'force-dynamic' for SSR per route.
  2. Nuxt — SSR is the default (ssr: true in nuxt.config). Verify it's not disabled.
  3. SvelteKit — SSR enabled by default; check +page.js doesn't set export const ssr = false.
  4. Astrooutput: 'server' in astro.config. Collections render server-side.
  5. Gatsby — SSG by default; every page is pre-rendered at build time. Verify pages aren't using window at import time (that breaks SSG).
  6. Vanilla React / Vue / Svelte — you need to add SSR: Vite has experimental SSR support; simpler path is migrate to Next/Nuxt/SvelteKit or use react-snap for pre-rendering.

How it pairs with the rest of the stack

When to actually invest in automation

If your site is primarily SPA-based AND rendered content differs materially from static, recurring monitoring becomes valuable. Options ranked by cost/effort:

  1. GitHub Actions + Playwright — free on public repos. One-time setup, daily cron, commits results to a JSON file the Trend Dashboard can ingest.
  2. Browserless.io free tier — 1000 renders/month free. Wire into a Netlify Function that calls their API and returns rendered HTML.
  3. Self-hosted Rendertron — on a $4/mo Hetzner / DO VPS. Fully controllable.
  4. Prerender.io — easiest drop-in, $15-115/mo.

For the one-off case that's 95% of use, the paste tool is enough.

Related reading

Fact-check notes and sources

This post is informational, not engineering advice. Mentions of Prerender.io, Browserless, Rendertron, Google, Next.js, Nuxt, SvelteKit, Astro, Gatsby, React, Vue, Svelte, and similar products are nominative fair use. No affiliation is implied.

← Back to Blog

Accessibility Options

Text Size
High Contrast
Reduce Motion
Reading Guide
Link Highlighting
Accessibility Statement

J.A. Watte is committed to ensuring digital accessibility for people with disabilities. This site conforms to WCAG 2.1 and 2.2 Level AA guidelines.

Measures Taken

  • Semantic HTML with proper heading hierarchy
  • ARIA labels and roles for interactive components
  • Color contrast ratios meeting WCAG AA (4.5:1)
  • Full keyboard navigation support
  • Skip navigation link
  • Visible focus indicators (3:1 contrast)
  • 44px minimum touch/click targets
  • Dark/light theme with system preference detection
  • Responsive design for all devices
  • Reduced motion support (CSS + toggle)
  • Text size customization (14px–20px)
  • Print stylesheet

Feedback

Contact: jwatte.com/contact

Full Accessibility StatementPrivacy Policy

Last updated: April 2026