WHY DOES A SILENTLY BROKEN PAGE RETURN HTTP 200? THE SILENT FAILURE DETECTOR
WHY DOES A SILENTLY BROKEN PAGE RETURN HTTP 200? THE SILENT FAILURE DETECTOR
The DOM watcher mechanism that catches content turning into a blank white screen while the server still looks healthy.
The Problem: 200 OK, but Nothing on the Page
A classic uptime check asks one question: is the server answering? If the answer is 200 OK, the light goes green. The trouble is that on a modern website, the server answering and the page working are no longer the same thing.
One night at 03:15, a contact page we were watching returned 200 OK in 612 milliseconds. The classic probe was happy. Yet on the page, the main content area that held 18 elements on the previous check now held zero. What a visitor saw was a pure white screen.
- The status code is not lying, it is leaving things out: The server really was up and really did send the HTML.
- The failure happens in the browser: The JavaScript bundle that builds the page threw a
TypeErrorand drew nothing. - Nobody notices: The uptime dashboard is green, and the client is the one who opens the site and meets a blank screen.
“The server being up does not mean the page is up.”
Why It Happens: The Server Is Up, the Browser Is Not
Most sites today build at least part of their content in the browser. The server sends a skeleton of HTML and a JavaScript bundle; the real content appears only after the bundle runs. In that chain, the status code is settled as soon as the server's part is done. Whatever happens afterwards, the code stays 200.
Silent crashes have a few recurring causes: a third-party script that stops being compatible after a plugin update, a bundle that fails to load from a CDN, a component that handles an empty API response without checking it. In every case the HTTP layer looks clean. In every case the visitor sees the same thing: nothing.
The Mechanism: Node Count Instead of Status Code
Detector's core is already built around watching one field on a page rather than the whole page. The silent failure check is the same idea read backwards: instead of watching a value change, watch a region empty out.
We open the page in its rendered state, meaning after the JavaScript has run. Then we count the visible elements under an important container, for example body > main, and compare that with the last healthy count. Uncaught errors from the console go into the record as well.
async def check_silent_fail(page, selector, baseline_count):
status = page.response_status
errors = page.console_errors()
count = await page.count_visible(selector)
if status == 200 and count == 0 and baseline_count > 0:
return {
"state": "blank_page",
"selector": selector,
"from": baseline_count,
"to": count,
"errors": errors[:3],
}
return {"state": "ok", "count": count}
Probe (5 min)
From two regions
Node count
body > main
Alert
With a screenshot
Noise: A False Alarm Kills the Alarm Itself
A blank screen alert is only useful if it can be trusted. After two alerts at 3 a.m. that turn out to be wrong, nobody opens the third. So we added three brakes:
- Second-region confirmation: An empty count from one region does not become an alert until a second region checks again. A passing network hiccup gets filtered out this way.
- Settling time: Some pages draw their content late. The count happens after the page settles; emptiness in the first half second does not count as a failure.
- Evidence: Every alert carries a screenshot of the moment and the first three console errors. Instead of "the site is broken" it says "TypeError in vendor.js, main area has 0 elements".
Then there is what silence means. In Detector, if a watched field can no longer be found, we do not wave it through as "no change"; we tell you the field has gone missing. Silence should mean "nothing changed", never "we stopped looking".
Where We Stand
Single-field watching is public in Detector today, and it tells you when a field cannot be resolved. The full site health check, which merges availability and content changes into one alert stream, is an open request on the request board collecting votes; it goes into review when it reaches 100. The mechanism above is what runs in that request's preview.
A page that breaks silently returns HTTP 200 because HTTP's job is already done by then. Ours starts right there.
NEXT POSTS
From our request board to production: how do community requests become products?
How we built a bot that tracks price changes on an e-commerce site in 15 minutes
Paying only the cost instead of SaaS subscriptions: Unit cost transparency
Got a Process Like This Too?
Tell us about the tedious manual task that costs your team at least 30 minutes every day; we will design a working in-browser micro tool for it in 48 hours.