Technical pillar / check js-render-parity

JS render-parity diff (raw HTML vs rendered DOM)

The raw HTML returned by a non-JS fetch should contain at least 70 percent of the content that a JS-rendered DOM contains, because AI crawlers do not execute JavaScript.

By Shimon Carroll, Founder, SEO for AI Agents · Last updated

What this check measures

The check fetches the page twice: once with the lightweight HTML fetcher (no JS execution, simulating GPTBot, ClaudeBot, PerplexityBot) and once with Playwright on Vercel Sandbox (full JS execution, simulating Googlebot). It measures the token-count ratio, the heading-presence ratio, and the shingle overlap between the two responses and emits a single render-parity score.

Why it matters

GPTBot, ClaudeBot, PerplexityBot, OAI-SearchBot, ChatGPT-User, and Google-Extended are documented (per OpenAI, Anthropic, and Perplexity's own published crawler docs as of late 2025) to fetch raw HTML without JS execution in their production paths. A page whose content lives in a React tree hydrated after page load is invisible to these crawlers. Classic Googlebot does render JS, but the rendering happens on a delayed queue, and Google's own documentation recommends server-rendered or static HTML for primary content. Render-parity below 70 percent is the strongest single predictor of zero AI citation rate we observe across launch-vertical audits.

How we score it

PASS if the parity ratio (raw HTML tokens / rendered DOM tokens) is at or above 0.70 AND every H1, H2 in the rendered DOM is present in the raw HTML. FAIL otherwise. Severity is HIGH by default and overridden to HIGH for ai_agencies on every page (the entire vertical depends on AI crawler visibility); MCA and accountants get HIGH on pricing and service pages; lawyers and doctors get HIGH on every practice-area / procedure page.

Confidence-flag rules

Confidence is HIGH when both fetches completed successfully and the raw fetch returned a 2xx. Confidence drops to MEDIUM when the Playwright render took longer than 10 seconds (the rendered DOM may not reflect what Googlebot would see on a normal-budget render). Confidence is LOW when either fetch returned a 4xx or 5xx; the finding is suppressed in that case.

Common mistakes

  • Building a Next.js App Router app entirely from client components, leaving the raw HTML with an empty <body> root container.
  • Loading the primary content via a useEffect fetch on the client, so the initial HTML returns navigation only.
  • Using a third-party widget (chat, IDX, configurator) that renders into a DOM container client-side, expecting the widget content to be indexed by AI crawlers.
  • Assuming "Googlebot renders JavaScript" generalizes to AI crawlers; it does not.

How to fix it

Move primary content out of client components and into Server Components, static export, or per-page getStaticProps / generateStaticParams. For Next.js App Router, prefer the default Server Component behavior and reserve client components for interactive islands (forms, modals, drawers). Verify with a curl that does not execute JavaScript ("curl -s https://yoursite/" piped to grep for the expected content). If a third-party widget cannot render server-side, add a static fallback with the equivalent content in the initial HTML.

Primary sources

Changelog

  • · Initial publication. Threshold set at 70 percent based on observed correlation between render-parity ratio and AI citation rate across launch-vertical audits.