A page can look complete in Chrome and still be mostly empty to Google. That gap exists because Googlebot doesn't index your site the way a visitor experiences it. It fetches the raw HTML first, queues the page for rendering later, and only then runs the JavaScript that fills in the content your users actually read. If that second pass gets delayed, skipped, or produces different output than the browser does, entire sections of a page can go missing from the index without a single error message telling you why.
This is one of the quieter ways technical SEO problems hide. Nothing crashes. No 404s show up in your logs. The page just quietly underperforms, and the usual explanations, thin content, weak backlinks, wrong keywords, don't actually apply. The real cause is a rendering gap, and it's diagnosable with the right process.
This guide covers how Googlebot's rendering pipeline actually works, the signals that tell you content isn't making it into the index, the tools for comparing rendered output against what a browser sees, and the fixes that close the gap for good.
Why JavaScript Rendering Breaks Assumptions About Crawling
Most people's mental model of crawling comes from static HTML: a bot requests a URL, reads the response, and indexes what it finds. That model still applies to plenty of the web, but any page that builds its content client-side, through a JavaScript framework, a client-side data fetch, or a hydration step, breaks it.
For those pages, the initial HTML response is often close to empty: a shell, a script tag, maybe a loading spinner. The actual content doesn't exist until JavaScript runs in a browser-like environment and constructs the DOM. Googlebot can do that, but it's a separate, more expensive step than fetching raw HTML, and it doesn't happen at the same time.
The practical result is that a site can be perfectly crawlable and still lose content in the handoff between fetching and rendering. Understanding that handoff is the first step to diagnosing it.

Photo by Ronaldo Guiraldelli on Pexels
How Googlebot's Two-Wave Indexing Actually Works
Google processes JavaScript-heavy pages in two passes. The first wave fetches and parses the raw HTML, extracts any links and content that are already present, and adds the URL to a rendering queue. The second wave, which can happen seconds or days later depending on capacity, runs the page through a headless rendering service similar to a modern browser and captures the resulting DOM.
Content that only exists after the second wave is at the mercy of that queue. If your site is large, if the rendering service is under load, or if the page takes a long time to become interactive, the gap between the first and second wave widens. During that gap, Google has already indexed the shell version of your page, and it's the shell version that can show up in search results until the render catches up.
This is why JavaScript SEO problems are inconsistent and hard to reproduce. A page might render correctly during a spot check with Google Search Central's tools one day and show up stripped of content in the index the next, simply because the two waves landed at different points in the page's lifecycle.
The Telltale Signs Your Content Isn't Being Rendered
A handful of symptoms usually show up together when rendering is the actual problem. Pages that should rank for obvious, unambiguous phrases from their own visible copy don't show up for those phrases at all. Search Console's coverage report shows pages as indexed, but a site: search or a cache comparison reveals a page that looks nothing like what a visitor sees.
Internal search performance on a page can also look strange: impressions exist for the URL, but for a much narrower or more generic set of queries than the actual content would justify, as if Google only ever saw the container and never the substance. New content added client-side, comments, reviews, related-product blocks, tends to be invisible in search results even months after publishing, while server-rendered elements on the same page rank fine.
None of these signs are conclusive on their own. Together, and especially when they cluster around pages built with the same framework or the same data-fetching pattern, they're a strong signal that the rendering pipeline, not the content itself, is the problem.
Using URL Inspection to See What Googlebot Sees
Search Console's URL Inspection tool is the most direct way to check rendering, because it shows you the actual rendered HTML Google produced for that URL, not a guess. Run a live test on a page you suspect is affected, then open the rendered HTML and search for text you know exists on the page in a normal browser.
If that text is missing from the rendered version, you have a confirmed rendering gap, not a hypothesis. It's worth testing a handful of page types across the site, not just one, since different templates or components can behave differently depending on how they fetch and mount data. A product page that hydrates instantly might render fine while a listings page that waits on three API calls in sequence does not.
The screenshot Search Console generates alongside the rendered HTML is also useful context: if the screenshot shows a mostly blank or loading-state page, that's a strong visual confirmation that Googlebot's render finished before your JavaScript did.
Diffing Rendered Output Against a Real Browser
Beyond spot-checking in Search Console, it helps to build a lightweight, repeatable comparison between what a real browser produces and what an automated crawler sees. Tools like a headless Node.js-based renderer can fetch a page, wait for it to become idle, and dump the resulting DOM for comparison against the same page's raw response.
Running that comparison across a sample of URLs, ideally one from each major template on the site, turns a vague suspicion into a measurable diff. You can track roughly how much visible text is present in the raw HTML versus the fully rendered version, and flag templates where that gap is large enough to matter.
This kind of check is worth automating into a release process rather than running once. A framework upgrade, a new client-side data library, or a change to how a component lazy-loads its content can quietly reintroduce a rendering gap that a one-time audit won't catch six months later.
Photo by Daniel Miksha on Unsplash
Common JavaScript Patterns That Trip Up Rendering
Certain implementation choices show up again and again in rendering audits. Content gated behind a user interaction, a tab click, an "expand" toggle, a modal, often never gets rendered at all, because Googlebot doesn't reliably simulate every possible interaction path a real user might take.
Infinite scroll is another repeat offender. If additional content only loads as a user scrolls, and there's no paginated or crawlable fallback, everything past the first viewport can be functionally invisible to a crawler that isn't scrolling indefinitely. Lazy-loaded images and below-the-fold text blocks that depend on an intersection observer firing behave the same way if there's no non-JavaScript fallback.
Client-side redirects and client-side-only routing can compound the problem further. If a route only resolves after JavaScript executes and reads the URL, a crawler that gives up before that point never reaches the content the route was supposed to serve.
Fixing It: Server Rendering and Static Generation
The most durable fix is to move content generation earlier in the pipeline, so the HTML response already contains the substance of the page before any JavaScript runs. Server-side rendering does this on every request; static site generation does it at build time and serves a pre-rendered file. Both approaches mean Googlebot's first wave, the fast HTML fetch, already captures the real content, and the second, slower rendering wave becomes a formality rather than a requirement.
Modern frameworks make this more approachable than it used to be. React and meta-frameworks built on top of it, including Next.js, support server rendering and static generation as first-class options rather than bolted-on workarounds, which is a large part of why they've become common recommendations for SEO-sensitive projects.
Where a full framework migration isn't realistic in the near term, dynamic rendering, serving a pre-rendered snapshot specifically to known crawlers while serving the normal client-rendered app to users, can close the gap as an interim measure. It adds operational complexity and shouldn't be treated as a permanent architecture, but it buys time to plan a proper fix.
Handling Lazy-Loaded Content Without Losing It to Crawlers
Lazy loading is good for performance and bad for crawlability when it's implemented without a fallback. The fix isn't to abandon lazy loading, it's to make sure the content still exists in the initial document, even if it's visually deferred, rather than only existing after a scroll or intersection event fires.
For images specifically, using the native loading="lazy" attribute on a real <img> tag with the correct src already present keeps the image in the DOM and discoverable, while still deferring the actual network fetch until the browser decides it's needed. That's a meaningfully different pattern from a JavaScript-driven lazy loader that only inserts the image element once a scroll event triggers.
The same principle applies to text content behind expandable sections. Rendering the full content in the DOM and using CSS to visually collapse it, rather than waiting to inject the content until a user clicks, keeps the material available to a crawler while preserving the collapsed interaction for visitors.

Photo by Pew Nguyen on Pexels
Building a Recurring Rendering Audit Into Your Workflow
A rendering check that happens once, after a problem is already suspected, misses the pattern that actually causes most regressions: an unrelated change somewhere else in the stack, a new dependency, an updated framework version, a component refactor, quietly changes how or when content mounts.
Treating the render diff described earlier as a recurring check, run against a stable set of representative URLs after significant deploys, turns rendering health into something you can trend over time instead of something you only discover after rankings drop. Pairing that with periodic URL Inspection spot checks on your highest-value pages adds a second, independent confirmation that Google's own renderer agrees with your automated diff.
"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
Where This Fits Into a Broader Technical SEO Practice
Rendering gaps rarely show up in isolation. Sites that struggle with JavaScript rendering often also carry redirect chains, inconsistent canonical signals, or crawl budget spent on low-value URLs, because all of these issues share a root cause: nobody is regularly checking what Google actually sees versus what the team assumes it sees.
Closing a single rendering gap is a good fix. Building the habit of checking, on every major release, is what keeps new gaps from reopening the moment a framework updates or a component gets rewritten. That habit is also where a lot of the value in ongoing technical SEO services comes from: not a one-time audit, but a standing process that catches these regressions before they cost months of visibility.
If your team is building on a modern JavaScript framework, it's worth pairing that rendering audit with a broader look at how the web development choices behind a page, the framework, the data-fetching pattern, the build pipeline, affect what search engines can actually see. The two problems are usually solved together, not separately.
Getting Started This Week
You don't need a full audit to start. Pick your three or four highest-traffic page templates, run each through Search Console's URL Inspection tool, and compare the rendered HTML against what you see in a normal browser tab. That alone will tell you whether this is a real problem on your site or a false alarm.
If you find a gap, prioritize fixing the templates with the most search-driven traffic first, since that's where the visibility cost is highest. From there, build the recurring diff check into your deploy process so the next framework upgrade doesn't quietly undo the fix.
Rendering problems are unusual among technical SEO issues in that they're almost entirely invisible until you go looking for them, and completely fixable once you do. Explore 137Foundry's full range of services if you'd rather have a team run this diagnostic for you.