GrabDiff
← Blog

HTTP 200 is not the same as 'your page is working'

May 2026 ·

I want to talk about a specific class of production incident that I keep seeing: the outage where your monitoring says everything is up, but your users are experiencing a broken or empty page.

The root cause, almost every time, is treating HTTP 200 as equivalent to "my page is working." It isn't. HTTP 200 means "the server received your request and sent back a response." It says nothing about what that response actually contains, whether JavaScript ran, whether the content loaded, or whether the page looks anything like what users expect to see.


What HTTP 200 actually tells you

The HTTP status code is determined by your server before it knows anything about what your client-side code will do with the response. A Next.js app returns 200 as soon as it successfully renders the HTML shell — which takes milliseconds, well before any data fetching or client-side rendering happens. A CDN returns 200 when it successfully serves a cached file — even if that cached file is a stale error page from three days ago.

Here's a simple mental model: HTTP 200 is your server saying "I understood the question." It is not saying "and here's a correct, complete answer."

The modern web made this worse

Ten years ago, most pages were server-rendered. When the server returned 200, the HTML in the response body was the page. If the database was down, the server would usually return a 500 or 503. There was a reasonable correlation between status codes and page health.

Now:

  • Single-page apps return a skeleton HTML file with 200, then render content with JavaScript
  • Incremental static regeneration serves stale cached pages with 200
  • API-dependent pages return 200 and then silently fail to populate if the API is down
  • Error boundaries in React catch crashes and render fallback UI, all with 200
  • CDNs serve cached content with 200 regardless of what the origin is currently returning

In every one of these cases, an HTTP monitor cheerfully reports "up" while users are looking at a broken page.

What you actually need to check

To know if a web page is genuinely working, you need to load it the same way a user would: in a real browser, with JavaScript execution, waiting for the content to render.

Then you need to verify the result. The simplest form of this is visual — take a screenshot, compare it against a known-good baseline. If the page looks the same, it's probably working. If it looks different — blank, missing content, different layout — something broke.

This is what visual uptime monitoring does. GrabDiff runs headless Chrome, loads your URL, takes a screenshot on a schedule, and diffs it against your baseline. The diff score tells you whether the page changed. A screenshot tells you what it looks like right now.

It's not a replacement for HTTP monitoring — you still want fast HTTP checks for detecting hard server failures. But for the "HTTP 200 but the page is broken" failure mode that's become so common with modern web architecture, you need to look at the page, not just the status code.