~ / insights / analysis

Analysis · September 2026

The Blank Page Googlebot Sees.

A site can pass PageSpeed, look perfect in Chrome and still hand Google an almost empty page. I found one, and the two patterns that caused it are spreading.

Amit TiwariAnalysis11 min read

The site had been rebuilt six months before I looked at it. New design, new theme, a performance plugin that had taken its PageSpeed score from the forties into the nineties. The marketing team was happy with the numbers and confused by the traffic, which had drifted down through the rebuild and kept drifting. Pages that described services in 800 words were not ranking for the services. Some had dropped out of the index entirely.

I opened one in URL Inspection, clicked View crawled page, and looked at the rendered HTML tab. The header and the hero were there, and below them every section was in the DOM with its text intact, and every one of them carried an inline style of opacity: 0. Google had the words. Google had also been told, by the page itself, that the words were not visible.

What the two patterns do on their own

Each of the two decisions is defensible in isolation, and each is usually made by a different person.

The first is script delay. A class of WordPress performance plugins, and a hand-rolled pattern that predates them, hold every third-party and non-critical script until the first user interaction. Nothing loads until the visitor scrolls, moves the mouse, touches the screen or presses a key. The reasoning is sound for Core Web Vitals. Scripts that never run before interaction cannot block the main thread before interaction, so Largest Contentful Paint and Interaction to Next Paint both improve, often dramatically. The plugin markets exactly this, and the PageSpeed score is the receipt.

The second is the scroll reveal. Animation libraries such as AOS, WOW.js and the reveal features built into most page builders work by setting an element’s starting state in CSS, usually opacity: 0 with a small translateY, and then adding a class when the element enters the viewport. The class transitions the element to opacity: 1. It looks polished, and it also means the starting state, the invisible one, is what the page looks like until the library’s JavaScript runs.

Put them together and the sequence is short. The reveal library is a script, the delay plugin holds all scripts until interaction, and the starting state is opacity zero. So until the visitor interacts, every section below the fold is invisible, and the visitor does not notice because their first scroll is the interaction that starts the scripts that reveal the sections. From a human’s chair the page works.

Interactive: toggle the delay plugin and the reveal library to see what the crawler snapshot receives. Needs JavaScript.

Each pattern alone is harmless. Together they hide the page from the snapshot. Delay plugin only Reveal library only Both together Below-fold text in HTML Reveal script loaded Sections at opacity 0 What the visitor sees Full page Full page Full page What the snapshot sees Full page Full page Hero only Reveal library alone: sections start hidden but the script runs at load and reveals them before the snapshot. Delay plugin alone: nothing is hidden to begin with.
Figure 1. The interaction of the two patterns. Neither team that chose one of them saw the third column.

What Googlebot does instead

Googlebot’s renderer is a headless Chromium that loads the page, waits for the network and script queue to settle within a budget, takes a snapshot of the DOM and moves on. Google’s documentation on JavaScript SEO describes the process and lists what the renderer does not do. It does not scroll, click, move a mouse or fire a touch event, because it has no hand to do any of those with, and its viewport is tall but its interaction count is zero.

So the delay plugin’s trigger never fires, the reveal library never loads, and the sections stay at opacity: 0. The snapshot Google indexes contains the text, and Google’s rendering also records that the text was not visible at the time of the snapshot. Google’s guidance since mobile-first indexing is that content in tabs and accordions counts in full, because a tap reveals it and the renderer can see the control that does the revealing. Text at opacity zero, with the script that would reveal it never loaded, has no control and no path to visibility in the snapshot at all. This page had 800 words of service copy and told the renderer that most of them were invisible.

What Googlebot's renderer does with a page Fetch HTML Run scripts until the queue settles No scroll, no click, no touch Snapshot the DOM Index what the snapshot shows as visible
Figure 2. The renderer is a camera on a timer, not a reader. Anything that needs an interaction to appear never appears in the snapshot.

The important word in the previous paragraph is “snapshot”. People imagine Googlebot as a patient reader that works its way down the page. It is closer to a camera on a timer. The page gets one exposure, and whatever the page looks like at that instant is what gets filed.

Why the score hid the problem

PageSpeed Insights and Lighthouse measure what a user experiences, and a user experiences a fast page with a nice animation. The lab run loads the page and measures paint timings. It does not check whether text is visible, because from a performance standpoint invisible text is cheaper than visible text. The delay plugin’s whole strategy is to move work out of the measured window, so the measured window looks excellent.

A green score and an indexed page are two different tests, and most agencies only run the first. The client had been sent the score as proof the rebuild had worked. Nobody had opened the rendered HTML.

The three-pass test

It takes about twenty minutes per template and needs nothing more than a terminal and a browser.

Pass one, raw HTML. Fetch the page the way a crawler fetches it before any JavaScript runs.

curl -sL -A "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36" https://example.com/service/ -o raw.html

Strip the tags and count the words. If the raw count is close to what you see in a browser, the content is server-rendered and pass one is clean. If the body is a shell like <div id="root"></div>, the content is client-rendered and everything depends on pass two. On this site the raw count was fine and the words were all in the HTML, which is exactly why nobody had suspected a rendering problem.

Pass two, rendered HTML. URL Inspection in Search Console gives you Google’s own rendered HTML for any URL on a property you control. Copy it out and count again. Or run a headless browser with a Googlebot smartphone user agent, no scroll, no interaction, and dump document.body.innerText. Compare with pass one. A large drop between raw and rendered means scripts are removing or replacing content. A large drop between rendered and what you see in Chrome means something is hiding it.

Pass three, visible at first paint. Almost nobody runs this pass, and it is the one that found the problem. In the rendered DOM, walk every element and add up the words inside anything with computed opacity: 0, visibility: hidden, or a transform that places it off-screen. That number, as a share of the rendered total, is how much of the page Google was told not to look at.

On this site the three numbers were, in round terms, all of the words in the raw HTML, all of the words in the rendered DOM, and under a fifth of the words visible at first paint. [VERIFY: exact counts from the audit]. The page was not missing, it was present and marked invisible, which is a worse position, because a missing page fails loudly and an invisible one fails quietly for months.

The three-pass test Pass 1: raw HTML word count Pass 2: rendered DOM word count Pass 3: words visible at first paint Verdict: share of the page Google was told to read
Figure 3. A drop between pass 1 and pass 2 means scripts removed content. A drop between pass 2 and pass 3 means CSS hid it. The second drop is the one PageSpeed cannot see.

Interactive: enter your three word counts for a verdict line. Needs JavaScript.

Why the pattern is spreading

Three things are pushing it into more sites at the same time, and none of them is going away.

Page builders now ship scroll reveal as a toggle, and the toggle defaults to a starting opacity of zero. A designer who wants the fade-in gets the invisible starting state for free and never sees the CSS.

Performance plugins have made script delay a one-click setting, and their documentation recommends it because the score improvement is immediate and visible. The plugins do warn, in the fine print, that scripts required for above-the-fold content should be excluded. Below-the-fold content is not mentioned, because from a performance perspective it does not matter.

And PageSpeed has become the report clients receive. A number that goes up is easy to sell. Rendered HTML is not a number, and the tab that shows it is four clicks deep in a tool most clients never open.

Once you know to look for the pair you find it in places that have nothing to do with each other. The render study I ran on 100 Indian IT services homepages, published alongside this piece, puts a number on it: the median homepage had 29 percent of its rendered words invisible at first paint, 26 of the 100 hid half or more, and the sites carrying a scroll-reveal library hid twice the share of those without one.

The fix that keeps the animation

The client did not want to lose the fade-in, and they did not need to. The animation was never the problem; the starting state was.

The rule is that the DOM Google snapshots must already be visible. Animate from visible to visible, or animate properties that do not affect visibility.

The cleanest version is to remove opacity from the starting state altogether and animate transform alone. A section that slides up eight pixels as it enters the viewport still reads as animated, and at first paint it is fully readable. If the designer insists on the fade, set the starting opacity in a class that is only applied once the reveal script has loaded, so a page where the script never runs shows everything. Most reveal libraries support this through an initialisation class. AOS, for example, only applies its hidden state to elements once aos-init is present on the root, and if that never happens the elements render normally. The site had been configured to apply the hidden state in the theme’s own CSS instead, which is the version that breaks.

Then exclude the reveal script from the delay plugin’s list, or drop it entirely, since a reveal library is a few kilobytes and delaying it saves nothing worth having while costing the rendered page.

Finally, add the three-pass check to the release checklist, next to the PageSpeed run, so the next redesign is tested for what Google sees and not only for how fast it sees it.

The fix keeps the animation and gives Google the words Before: starting state invisible .section { opacity: 0; transform: translateY(8px) .section.in-view { opacity: 1; transform: none } Reveal script held by the delay plugin Snapshot: text present, opacity 0 After: animate transform only .section { transform: translateY(8px) } .section.in-view { transform: none } Reveal script excluded from delay, or dropped Snapshot: text present and visible If the designer insists on the fade, apply the opacity-0 class only once the reveal script has initialised, so a page where it never runs shows everything.
Figure 4. Two lines of CSS and one plugin exclusion. The starting state was the problem, never the animation.

[VERIFY: what happened after the fix on this site, with dates. Write it plainly or leave it out. No recovery claim goes in without the Search Console export behind it.]

What this means if you run a site

If your site had a performance plugin installed in the last two years and your theme has any kind of scroll animation, assume the problem exists until pass three says otherwise. The check is cheap, and the cost of not checking is a page that looks finished, scores well, and ranks for nothing.

If you buy SEO or web development from an agency, ask for the rendered HTML word count alongside the PageSpeed score. An agency that cannot produce it has not looked.

Where I could be wrong

Google’s handling of hidden content is described in its documentation in general terms, and the exact weighting given to opacity-zero text is not published. It is possible that on some pages the content is indexed at full weight regardless, and the ranking loss on this site had a second cause I did not isolate. The recovery after the fix is consistent with the diagnosis but does not prove it on its own.

The renderer’s behaviour also changes. If Google’s renderer begins firing synthetic interactions or scrolling before its snapshot, the delay-plugin half of the problem disappears. As of the date of this piece the documentation still lists scrolling and clicking as things the renderer does not do.

Some delay plugins exclude scripts by pattern and ship reveal libraries in their default exclusion list. If yours does, the problem may only appear once a developer adds a custom reveal script that the pattern does not match, which is what happened here.

Sources

  • Google Search Central, Understand JavaScript SEO basics: https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics
  • Google Search Central, Fix Search-related JavaScript problems: https://developers.google.com/search/docs/crawling-indexing/javascript/fix-search-javascript
  • Google Search Central, URL Inspection tool, view the rendered page: https://support.google.com/webmasters/answer/9012289
  • AOS, Animate On Scroll library documentation: https://michalsnik.github.io/aos/
  • WP Rocket documentation, Delay JavaScript execution: https://docs.wp-rocket.me/article/1349-delay-javascript-execution

How to cite this analysis

Amit Tiwari (2026). The Blank Page Googlebot Sees. Analysis, September 2026. amittiwari.net. https://amittiwari.net/analysis/the-blank-page-googlebot-sees

Discuss in the community ↗