DEV Community

137Foundry
137Foundry

Posted on

How to Build a Rendering Diff Script With Node.js

Checking JavaScript SEO rendering by hand, one URL at a time in Search Console, works fine for a handful of pages. It falls apart once you have dozens of templates and want to catch regressions automatically after every deploy. A small Node.js script that fetches raw HTML and fully rendered HTML for the same URL, then diffs the visible text between them, turns that manual process into something you can run in CI.

This walks through building that script from scratch, step by step, using tools most JavaScript teams already have available.

Step 1: Decide What "Rendered" Means for Your Comparison

Before writing any code, decide what you're actually comparing. Raw HTML is straightforward: it's the literal response body from a plain HTTP request, before any script executes. Rendered HTML needs a browser-like environment to produce, since it requires actually executing JavaScript and waiting for the page to settle.

For this script, a headless browser running via Node.js is the standard approach: fetch the raw response first with a simple HTTP client, then load the same URL in a headless browser instance, wait for network activity to go idle, and capture the resulting DOM as your rendered version.

Step 2: Fetch the Raw HTML

Start with the simplest half of the comparison. A plain HTTP GET request, using Node's built-in fetch or a lightweight HTTP client, retrieves the server's initial response exactly as any crawler's first wave would see it.

async function getRawHtml(url) {
  const response = await fetch(url);
  return await response.text();
}
Enter fullscreen mode Exit fullscreen mode

Store this response as-is. It's your baseline for "what a crawler sees before rendering happens," and it should not go through any further processing at this stage.

Step 3: Fetch the Rendered HTML

For the rendered version, load the same URL in a headless browser, wait until the page has genuinely finished its client-side work, and pull the resulting DOM as a string. The specific waiting strategy matters here: waiting a fixed number of seconds is fragile, since some pages settle in under a second and others take much longer depending on data fetching.

A more reliable approach waits for network activity to go idle for a short window, which approximates what a patient renderer would do before giving up. Once that condition is met, serialize the live DOM back into an HTML string for comparison.

Step 4: Extract Visible Text From Both Versions

Comparing raw markup directly produces a lot of noise, since whitespace, attribute ordering, and script tags differ even between two versions of the same fundamentally-identical page. What actually matters for SEO purposes is the visible text content, so parse both HTML strings and extract just the text nodes a user would actually read, stripping out script and style content, hidden elements, and markup structure.

This gives you two plain-text strings: one representing what exists before any JavaScript runs, and one representing what exists after the page fully settles.

Step 5: Compute and Report the Diff

With two clean text strings in hand, a simple length and content comparison already tells you a lot. Calculate the character or word count of each, and flag any URL where the rendered version is dramatically larger than the raw version, since that gap is exactly the content that depends entirely on JavaScript executing successfully.

function reportGap(url, rawText, renderedText) {
  const gap = renderedText.length - rawText.length;
  const gapPercent = Math.round((gap / renderedText.length) * 100);
  if (gapPercent > 40) {
    console.warn(`${url}: ${gapPercent}% of visible text is render-dependent`);
  }
}
Enter fullscreen mode Exit fullscreen mode

The exact threshold worth flagging depends on your site, but a page where more than a third or so of its visible text only exists after rendering is worth a closer manual look, especially if that page is meant to rank for specific, content-driven queries.

Step 6: Run It Against a Representative URL Set

A diff script is only useful if you run it against the right pages. Pick one URL per major template, product pages, listing pages, article pages, category pages, rather than trying to check every URL on the site. Templates behave consistently within themselves, so a representative sample catches template-level regressions without the overhead of crawling the entire site on every run.

Step 7: Wire It Into Your Deploy Process

The real value of this script comes from running it automatically rather than remembering to run it manually. Adding it as a post-deploy check, even a simple one that just logs a warning rather than blocking a release, means a framework upgrade or a new data-fetching library that quietly increases the rendering gap gets caught within a day instead of a quarter later when rankings have already dropped.

Step 8: Handle False Positives Before You Trust the Output

Not every large gap between raw and rendered text is actually a problem. Pages with heavy client-side personalization, a logged-in greeting, a recommendation widget seeded from cookies, will naturally show a bigger gap without necessarily hiding content that matters for search. Filtering these known, expected differences out of your reporting keeps the script's warnings meaningful instead of noisy enough that people start ignoring them.

A practical way to handle this is maintaining a short allowlist of selectors or content regions known to be legitimately personalized or non-indexable, and excluding them from the text extraction step before computing the gap. Anything outside that allowlist that still shows a large gap is a much stronger signal of a genuine problem worth investigating, rather than an expected side effect of a feature working as designed.

It's also worth logging the actual missing text snippets, not just a percentage, the first few times you run this against a new template. Skimming what specifically didn't render often reveals the root cause immediately, a specific component, a specific data dependency, faster than trying to reason about it from a bare number.

Keeping the Script Maintainable Over Time

A diff script that nobody updates eventually drifts out of sync with the site it's checking, especially as new templates get added or existing ones get restructured. Treat the list of representative URLs it checks as something that needs periodic review, roughly every quarter, or any time a new page template ships, rather than a fixed list set once and forgotten.

It's also worth versioning the thresholds you flag on, since a threshold that made sense for a mostly-static site can be too sensitive once a team intentionally adopts more client-side personalization for legitimate reasons. Revisiting the threshold occasionally, alongside a spot check against what URL Inspection reports, keeps the script's warnings trustworthy rather than something the team learns to tune out.

Where to Go From Here

This script gives you a repeatable, code-based signal for rendering health, but it's a complement to spot-checking with Google Search Central's own URL Inspection tool, not a replacement for it. Search Console shows you what Google's actual renderer produced; your own diff script shows you trends across many pages over time.

web.dev has additional guidance on rendering strategies worth reading once you've identified which templates are carrying the largest gaps, and MDN's documentation is useful for understanding exactly how the DOM gets constructed in whatever headless environment you choose for the rendered fetch.

"Teams treat JavaScript rendering as a browser problem, but Googlebot's renderer is really a second, slower browser with its own queue and its own budget. If you're not regularly checking what it actually sees, you're optimizing for a version of your site that doesn't exist in the index." - Dennis Traina, founder of 137Foundry

If you want the fuller background on why this gap exists in the first place and how Google's two-wave indexing process works, this deep dive on JavaScript rendering and SEO walks through the mechanics behind the numbers this script produces. The team at 137Foundry builds checks like this into ongoing technical SEO work rather than treating them as a one-time audit.

Top comments (0)