<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sitewatch</title>
    <description>The latest articles on DEV Community by Sitewatch (@sitewatch).</description>
    <link>https://dev.to/sitewatch</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3844486%2F90af6851-c32d-48f6-a35b-3f4475706a06.png</url>
      <title>DEV Community: Sitewatch</title>
      <link>https://dev.to/sitewatch</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sitewatch"/>
    <language>en</language>
    <item>
      <title>We Built a Free Website Health Scanner. Here's What It Finds on Most Sites</title>
      <dc:creator>Sitewatch</dc:creator>
      <pubDate>Thu, 16 Apr 2026 09:24:13 +0000</pubDate>
      <link>https://dev.to/sitewatch/we-built-a-free-website-health-scanner-heres-what-it-finds-on-most-sites-179c</link>
      <guid>https://dev.to/sitewatch/we-built-a-free-website-health-scanner-heres-what-it-finds-on-most-sites-179c</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Your site is up. But is it healthy?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We just shipped a free website health scanner at &lt;a href="https://getsitewatch.com/scan?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=free-scanner" rel="noopener noreferrer"&gt;getsitewatch.com/scan&lt;/a&gt;. No signup. No email. Just paste a URL and get a full report in about 20 seconds.&lt;/p&gt;

&lt;p&gt;We built it because we kept running into the same problem: developers and site owners had no quick way to check if their site was &lt;em&gt;actually&lt;/em&gt; working — beyond just loading the page in a browser and eyeballing it.&lt;/p&gt;

&lt;p&gt;Here's what the scanner checks, why each issue matters, and what we typically find when people scan their production sites.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Broken Assets (Images, Scripts, Stylesheets)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we check:&lt;/strong&gt; Every image, script, and stylesheet referenced in your HTML. Does it actually load? Or does it return a 404, 403, or 500?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we typically find:&lt;/strong&gt; This is the most common issue. Roughly 3 out of 10 sites we've scanned have at least one broken asset. Usually it's an image that was moved or renamed, a script that was deleted during a deploy, or a stylesheet that references a font file that no longer exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; A missing image is cosmetic. A missing JavaScript file can break your entire page. If your checkout depends on a script that returns 404, your checkout is dead — but your server still returns 200 OK and your uptime monitor says everything is fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick manual check:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Find broken images on any page&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://yoursite.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oP&lt;/span&gt; &lt;span class="s1"&gt;'src="[^"]*"'&lt;/span&gt; | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; src&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/src=//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; http&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://yoursite.com&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"200"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"BROKEN (&lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="s2"&gt;): &lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just paste your URL at &lt;a href="https://getsitewatch.com/scan?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=free-scanner" rel="noopener noreferrer"&gt;getsitewatch.com/scan&lt;/a&gt; — it does this automatically for every asset type.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Mixed Content
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we check:&lt;/strong&gt; Does your HTTPS page load any resources over plain HTTP?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we typically find:&lt;/strong&gt; Common on sites that migrated to HTTPS but still have hardcoded &lt;code&gt;http://&lt;/code&gt; URLs in the database, templates, or third-party embeds. WordPress sites are particularly prone to this after an SSL migration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Modern browsers silently block mixed content. No error. No warning to the user. Images just don't render. Scripts don't execute. Fonts fall back to system defaults. Your site looks broken, but only to your visitors — not to you in your browser with cached assets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick manual check:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://yoursite.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oP&lt;/span&gt; &lt;span class="s1"&gt;'(src|href)="http://[^"]*"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this returns anything on an HTTPS site, those resources are being blocked.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. SSL Certificate Issues
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we check:&lt;/strong&gt; Is your certificate valid? When does it expire? Is the chain complete? Does it match the domain?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we typically find:&lt;/strong&gt; Certificates expiring within 30 days without auto-renewal configured. Incomplete chains where the intermediate CA is missing — site works in Chrome but throws warnings in Safari and on mobile. Certs that don't match the domain after a server migration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; An expired or misconfigured cert turns your site into a browser warning page. But the failure window before it fully expires is worse — some browsers are more forgiving than others, so you get inconsistent behavior that's hard to debug. According to Keyfactor's 2024 report, 88% of companies experienced an unplanned outage from an expired certificate in the past two years.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick manual check:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check when your cert expires&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; | openssl s_client &lt;span class="nt"&gt;-servername&lt;/span&gt; yoursite.com &lt;span class="nt"&gt;-connect&lt;/span&gt; yoursite.com:443 2&amp;gt;/dev/null | openssl x509 &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-enddate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scanner flags certificates expiring within 30 days and checks chain completeness automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Missing Security Headers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we check:&lt;/strong&gt; CSP (Content-Security-Policy), HSTS (Strict-Transport-Security), X-Frame-Options, X-Content-Type-Options, Referrer-Policy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we typically find:&lt;/strong&gt; This is the most universally failed check. The vast majority of sites we scan are missing at least two critical security headers. HSTS and CSP are the most commonly absent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No HSTS:&lt;/strong&gt; Visitors can be MITM'd on their first visit before the redirect to HTTPS happens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No CSP:&lt;/strong&gt; No defense against XSS attacks injecting scripts into your page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No X-Frame-Options:&lt;/strong&gt; Your site can be embedded in an iframe on a malicious page (clickjacking).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No X-Content-Type-Options:&lt;/strong&gt; Browsers may MIME-sniff responses and execute files as scripts that weren't intended to be scripts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quick manual check:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://yoursite.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s2"&gt;"strict-transport|content-security|x-frame|x-content-type|referrer-policy"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Empty result = no security headers. The scanner shows which ones are missing and what each one protects against.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Schema &amp;amp; SEO Issues
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we check:&lt;/strong&gt; JSON-LD structured data validation, canonical URLs, Open Graph tags, meta descriptions, title tags.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we typically find:&lt;/strong&gt; Missing or malformed JSON-LD is common — especially after a CMS update or template change. Missing Open Graph tags mean your site looks bare when shared on LinkedIn, Twitter, or Slack. Duplicate or missing canonical URLs confuse search engines about which page to index.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; These don't break your site visually, but they break your site's &lt;em&gt;reach&lt;/em&gt;. Bad schema means Google can't generate rich snippets. Missing OG tags mean your links look unprofessional when shared. Wrong canonicals mean your SEO juice is split across duplicate pages.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Discoverability
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we check:&lt;/strong&gt; Does &lt;code&gt;robots.txt&lt;/code&gt; exist and is it valid? Is there a &lt;code&gt;sitemap.xml&lt;/code&gt; and does it return 200?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we typically find:&lt;/strong&gt; Missing sitemaps are surprisingly common — especially on SPAs and Jamstack sites where nobody thought to generate one. Occasionally we find &lt;code&gt;robots.txt&lt;/code&gt; files that accidentally &lt;code&gt;Disallow: /&lt;/code&gt; — blocking all search engines from the entire site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Without a sitemap, search engines crawl your site less efficiently. A misconfigured &lt;code&gt;robots.txt&lt;/code&gt; can de-index your entire site. These are 5-minute fixes with massive impact.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Report Looks Like
&lt;/h2&gt;

&lt;p&gt;The scanner returns a prioritized report with severity rankings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Critical&lt;/strong&gt; — Things actively breaking your site for visitors (broken scripts, expired SSL)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Warning&lt;/strong&gt; — Things silently degrading the experience (mixed content, missing security headers)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Info&lt;/strong&gt; — Things that affect SEO and discoverability (missing schema, no sitemap)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every finding includes a plain-language explanation of &lt;em&gt;what&lt;/em&gt; it is, &lt;em&gt;why&lt;/em&gt; it matters, and &lt;em&gt;how&lt;/em&gt; to fix it. No jargon dumps. No "error code 4xx on subresource" without context.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why We Built This
&lt;/h2&gt;

&lt;p&gt;We run &lt;a href="https://getsitewatch.com?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=free-scanner" rel="noopener noreferrer"&gt;Sitewatch&lt;/a&gt; — a website monitoring tool that continuously checks if sites actually work, not just if they respond.&lt;/p&gt;

&lt;p&gt;But continuous monitoring is a commitment. You need to sign up, configure checks, set up alerts. That's the right move for production sites — but sometimes you just want a quick answer: &lt;strong&gt;is my site healthy right now?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The scanner is that quick answer. Think of it as a one-time checkup versus ongoing monitoring. The doctor visit versus the daily vitamin.&lt;/p&gt;

&lt;p&gt;It's completely free. No email required. No credit card. No "enter your email to see the full results" nonsense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://getsitewatch.com/scan?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=free-scanner" rel="noopener noreferrer"&gt;Scan your site now →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Happens After the Scan
&lt;/h2&gt;

&lt;p&gt;If your site is clean — great. You just got peace of mind in 20 seconds.&lt;/p&gt;

&lt;p&gt;If it found issues — the report tells you exactly what to fix and why, ordered by severity. Most of the critical issues (broken assets, SSL problems) can be fixed in under an hour.&lt;/p&gt;

&lt;p&gt;And if you want to make sure these issues don't come &lt;em&gt;back&lt;/em&gt; after the next deploy, that's where continuous monitoring comes in. Sitewatch checks your site on an ongoing basis and alerts you before your users notice something is broken.&lt;/p&gt;

&lt;p&gt;But the scan itself? No strings attached. Use it, fix what it finds, move on.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Try it on your own site and drop your health rating in the comments — I'm curious what people find.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://getsitewatch.com/scan?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=free-scanner" rel="noopener noreferrer"&gt;getsitewatch.com/scan&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>monitoring</category>
      <category>webperf</category>
      <category>security</category>
    </item>
    <item>
      <title>We Stopped Trusting Uptime Metrics. Here's What We Monitor Instead.</title>
      <dc:creator>Sitewatch</dc:creator>
      <pubDate>Tue, 31 Mar 2026 11:31:26 +0000</pubDate>
      <link>https://dev.to/sitewatch/we-stopped-trusting-uptime-metrics-heres-what-we-monitor-instead-1ob7</link>
      <guid>https://dev.to/sitewatch/we-stopped-trusting-uptime-metrics-heres-what-we-monitor-instead-1ob7</guid>
      <description>&lt;p&gt;We're going to make a claim that might sound controversial:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Uptime monitoring and website monitoring are not the same thing.&lt;/strong&gt; Most people use the terms interchangeably. They shouldn't.&lt;/p&gt;

&lt;p&gt;Uptime monitoring answers: "Did the server respond?"&lt;br&gt;
Website monitoring answers: "Does the site work?"&lt;/p&gt;

&lt;p&gt;Those sound similar. They're not. And the gap between them is where most silent failures live.&lt;/p&gt;




&lt;h2&gt;
  
  
  What uptime monitoring actually does
&lt;/h2&gt;

&lt;p&gt;Let's be specific about what a typical uptime monitoring tool checks. There's no ambiguity here — this is the standard model that tools like UptimeRobot, Pingdom, Better Stack, and most others follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send an HTTP request to your URL&lt;/li&gt;
&lt;li&gt;Receive a response&lt;/li&gt;
&lt;li&gt;Check the status code&lt;/li&gt;
&lt;li&gt;If it's &lt;code&gt;200 OK&lt;/code&gt; → mark as healthy&lt;/li&gt;
&lt;li&gt;If it's &lt;code&gt;5xx&lt;/code&gt; or timeout → mark as down&lt;/li&gt;
&lt;li&gt;Report uptime percentage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. That's the check.&lt;/p&gt;

&lt;p&gt;It tells you whether your server is alive and responding to requests. It does that well. And for a long time, that was enough — because if the server was up, the site worked.&lt;/p&gt;

&lt;p&gt;That's no longer true.&lt;/p&gt;




&lt;h2&gt;
  
  
  What uptime monitoring does NOT check
&lt;/h2&gt;

&lt;p&gt;Here's a non-exhaustive list of things a standard uptime check will miss:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asset integrity.&lt;/strong&gt; Your HTML loads, but the JavaScript bundle it references returns 404 because you deployed and the CDN still serves old HTML pointing to deleted files. The page is a blank white screen. Status code on the document: 200.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MIME types.&lt;/strong&gt; Your server returns a JS file with &lt;code&gt;Content-Type: text/html&lt;/code&gt;. The browser downloads it, reads the header, silently refuses to execute it. Your SPA never boots. Status: 200.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redirect chains.&lt;/strong&gt; Your site redirects &lt;code&gt;/page&lt;/code&gt; → &lt;code&gt;/page/&lt;/code&gt; → &lt;code&gt;/page&lt;/code&gt; → &lt;code&gt;/page/&lt;/code&gt;. The browser gives up after 20 hops. Your uptime tool followed the first redirect, got a 200, and moved on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content correctness.&lt;/strong&gt; Someone changed your homepage in the CMS at 2am. Or your CDN started serving content from the wrong origin after a migration. The page loads — it's just the wrong page. Status: 200.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third-party dependencies.&lt;/strong&gt; Stripe's JS fails to load. Your checkout renders as an empty div. Your auth provider is down. Users can't log in. Your page loaded fine — the broken resource is on someone else's domain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regional divergence.&lt;/strong&gt; Your site works from US-East. In Frankfurt, the CDN edge serves stale cached assets from three deploys ago. Single-region uptime checks never see this.&lt;/p&gt;

&lt;p&gt;Every single one of these returns &lt;code&gt;200 OK&lt;/code&gt;. Every single one passes a standard uptime check. Every single one is broken for users.&lt;/p&gt;




&lt;h2&gt;
  
  
  What website monitoring checks instead
&lt;/h2&gt;

&lt;p&gt;Website monitoring starts where uptime monitoring stops. Instead of asking "did the server respond?", it asks "did the response constitute a working page?"&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treating the HTML document as a manifest, not a destination.&lt;/strong&gt; The document is step one. Website monitoring follows up — do the JS and CSS bundles it references actually load? Do they return the right MIME types? Do they return valid status codes?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Following redirect chains to completion.&lt;/strong&gt; Not just the first hop. The full chain. Does it resolve? Does it loop? Does it end somewhere unexpected?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fingerprinting content over time.&lt;/strong&gt; If the response body changes without a deployment, you want to know. Content fingerprinting with SHA-256 hashes catches silent CMS edits, CDN origin drift, and cache poisoning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Checking from multiple regions.&lt;/strong&gt; CDN failures are regional by nature. A site can be perfect in one location and broken in another. Website monitoring runs the full check suite from multiple geographic locations independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Classifying root cause.&lt;/strong&gt; Not just "something is wrong" but "why." Is this a deployment artifact? A CDN cache issue? A DNS drift? An application error? Root cause classification turns an alert into actionable information.&lt;/p&gt;




&lt;h2&gt;
  
  
  A practical comparison
&lt;/h2&gt;

&lt;p&gt;Here's the same scenario through both lenses:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; You deploy a Nuxt 3 app to Vercel. The build succeeds. But a CDN edge node in Europe still serves the previous HTML, which references &lt;code&gt;app.7fb2e.js&lt;/code&gt;. That file was deleted in the new build.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Uptime monitoring&lt;/th&gt;
&lt;th&gt;Website monitoring&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTTP status&lt;/td&gt;
&lt;td&gt;200 OK ✅&lt;/td&gt;
&lt;td&gt;200 OK (document)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asset check&lt;/td&gt;
&lt;td&gt;Not checked&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;app.7fb2e.js&lt;/code&gt; → 404 ❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MIME validation&lt;/td&gt;
&lt;td&gt;Not checked&lt;/td&gt;
&lt;td&gt;N/A (asset missing)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-region&lt;/td&gt;
&lt;td&gt;Single region ✅&lt;/td&gt;
&lt;td&gt;EU: broken ❌, US: ok ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alert&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Incident: deployment artifact missing, EU region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root cause&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;CDN cache stale, Vercel ISR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User impact&lt;/td&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;td&gt;European users see blank page&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same site. Same moment. Completely different picture.&lt;/p&gt;




&lt;h2&gt;
  
  
  They're complementary, not competing
&lt;/h2&gt;

&lt;p&gt;We want to be clear about something: we're not saying uptime monitoring is useless. It's not. You absolutely need to know when your server is down. A &lt;code&gt;503&lt;/code&gt; or a timeout is a real outage and you need to know about it immediately.&lt;/p&gt;

&lt;p&gt;The problem is treating uptime monitoring as &lt;em&gt;sufficient&lt;/em&gt;. It's one layer. It catches one type of failure. The failures it misses — the "200 OK but broken" failures — are increasingly the ones that actually hurt users.&lt;/p&gt;

&lt;p&gt;The monitoring stack for a modern web application should have both:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Uptime monitoring&lt;/strong&gt; → Is the server alive? Is it responding? Is the infrastructure running?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website monitoring&lt;/strong&gt; → Does the site work? Are assets loading? Is the content correct? Does it work everywhere?&lt;/p&gt;

&lt;p&gt;If you only have the first, you have a blind spot. And it's the kind of blind spot where your dashboard says 99.9% uptime while your users are staring at blank pages.&lt;/p&gt;




&lt;h2&gt;
  
  
  This is why we built Sitewatch
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://getsitewatch.com?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=uptime-vs-website" rel="noopener noreferrer"&gt;Sitewatch&lt;/a&gt; is website monitoring. Not uptime monitoring. The distinction matters to us because it's the entire reason the product exists.&lt;/p&gt;

&lt;p&gt;We check asset integrity, MIME types, redirect chains, content fingerprints, and multi-region consistency. When something breaks, we classify root cause — infrastructure, application, or content delivery — and provide fix guidance tailored to your tech stack.&lt;/p&gt;

&lt;p&gt;Every check runs a 2-of-3 retry confirmation model so you don't get woken up at 3am for a transient CDN hiccup. Duplicate alerts are deduplicated with SHA-256 fingerprinting and a 30-minute cooldown.&lt;/p&gt;

&lt;p&gt;Free tier for 1 site. No credit card. Takes 30 seconds: &lt;a href="https://getsitewatch.com?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=uptime-vs-website" rel="noopener noreferrer"&gt;getsitewatch.com&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Uptime monitoring tells you the server is alive. Website monitoring tells you the site works. They're not the same question — and the answer to one doesn't imply the answer to the other.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>devops</category>
    </item>
    <item>
      <title>Uptime Monitoring Is a Lie. Here's What It Actually Misses</title>
      <dc:creator>Sitewatch</dc:creator>
      <pubDate>Sat, 28 Mar 2026 08:16:01 +0000</pubDate>
      <link>https://dev.to/sitewatch/uptime-monitoring-is-a-lie-heres-what-it-actually-misses-1258</link>
      <guid>https://dev.to/sitewatch/uptime-monitoring-is-a-lie-heres-what-it-actually-misses-1258</guid>
      <description>&lt;p&gt;If your uptime dashboard says 99.9%, congratulations. Your server responded to pings 99.9% of the time.&lt;/p&gt;

&lt;p&gt;That number says nothing about whether your users could actually use your site.&lt;/p&gt;

&lt;p&gt;The monitoring industry has spent two decades optimizing for the wrong metric. We got really, really good at answering "did the server respond?" — and never moved on to the question that actually matters: "does the site work?"&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap nobody talks about
&lt;/h2&gt;

&lt;p&gt;There's a growing space between what monitoring tools measure and what users actually experience. It's getting wider, not smaller, because modern web architecture is getting more complex.&lt;/p&gt;

&lt;p&gt;Ten years ago, your site was a server rendering HTML. If the server was up, the site worked. Uptime monitoring made sense.&lt;/p&gt;

&lt;p&gt;Today, your "site" is a document that references hashed JS bundles from a CDN, loads third-party scripts from five different domains, runs through an API gateway, gets assembled by edge functions, and depends on a headless CMS for content. The server responding is step one of a twelve-step process.&lt;/p&gt;

&lt;p&gt;Uptime monitoring still checks step one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "up" actually looks like in production
&lt;/h2&gt;

&lt;p&gt;Let's say your uptime monitor pings your site every 60 seconds. Here's what it does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sends an HTTP request&lt;/li&gt;
&lt;li&gt;Gets a response&lt;/li&gt;
&lt;li&gt;Checks the status code&lt;/li&gt;
&lt;li&gt;If it's 200, marks the site as healthy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's what it doesn't do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check if the JavaScript bundles the page references actually exist&lt;/li&gt;
&lt;li&gt;Verify that assets are served with correct MIME types&lt;/li&gt;
&lt;li&gt;Follow redirect chains beyond the first hop&lt;/li&gt;
&lt;li&gt;Compare the response content to what it looked like yesterday&lt;/li&gt;
&lt;li&gt;Check whether third-party dependencies loaded&lt;/li&gt;
&lt;li&gt;Verify the page works from more than one location&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every single one of those unchecked items is a real failure mode that happens in production regularly. And every single one returns HTTP 200 OK.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three failure patterns your uptime tool will never catch
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The stale CDN.&lt;/strong&gt;&lt;br&gt;
You deploy. Your CDN edge nodes in some regions still serve old HTML. That HTML references JS bundles that no longer exist. The document loads, the scripts 404, nothing renders. Some users get it, others don't. Your monitor checks from one region and sees a healthy page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The MIME mismatch.&lt;/strong&gt;&lt;br&gt;
A misconfigured proxy serves your JavaScript file with &lt;code&gt;Content-Type: text/html&lt;/code&gt;. The browser downloads it, reads the header, and silently refuses to execute it. No console error. No failed network request. Just a page that never initializes. Status: 200.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The silent dependency failure.&lt;/strong&gt;&lt;br&gt;
Your site loads fine. Your payment provider's JS doesn't. The checkout button renders as an empty container. Your page looks complete. It just can't process transactions. Your uptime tool checks your domain — the broken resource is on someone else's.&lt;/p&gt;

&lt;p&gt;None of these are edge cases. They're happening on production sites right now. The sites report 99.9% uptime while users experience broken functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the industry is stuck
&lt;/h2&gt;

&lt;p&gt;Uptime monitoring became a commodity. Dozens of tools compete on the same feature set: more check locations, faster intervals, prettier dashboards. But they all measure the same thing — did the server respond with a non-error status code.&lt;/p&gt;

&lt;p&gt;The underlying assumption hasn't changed since 2005: a healthy HTTP response means a healthy website.&lt;/p&gt;

&lt;p&gt;That assumption was wrong then. It's dangerously wrong now.&lt;/p&gt;

&lt;p&gt;Part of the reason is that deeper monitoring is harder. Verifying asset integrity means parsing HTML, extracting resource references, and checking each one. Following redirects properly means handling cookie state and user agent differences. Detecting content drift means fingerprinting responses over time. Checking from multiple regions means actually running infrastructure in multiple regions.&lt;/p&gt;

&lt;p&gt;It's more expensive to build and more expensive to run. So most tools don't do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What would real monitoring look like?
&lt;/h2&gt;

&lt;p&gt;If we started from scratch — knowing what we know about how modern sites actually fail — monitoring would look completely different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It would treat the HTML document as a manifest, not a destination.&lt;/strong&gt; The document is the starting point. The real question is whether everything it references actually loads and works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It would verify, not assume.&lt;/strong&gt; Instead of trusting that a 200 means everything is fine, it would check that JS bundles return the right MIME type, that redirects resolve instead of loop, that the content hasn't silently changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It would check from multiple locations.&lt;/strong&gt; CDN failures are regional. A site can be perfectly functional in one region and completely broken in another. Single-region checking misses this entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It would distinguish between "responded" and "works."&lt;/strong&gt; Two fundamentally different states that current monitoring treats as one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It would reduce noise, not create it.&lt;/strong&gt; False positives at 3am erode trust faster than anything. A proper confirmation model — checking multiple times before alerting — means you only get woken up for real problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is why we built Sitewatch
&lt;/h2&gt;

&lt;p&gt;We got tired of the gap between what uptime dashboards reported and what users actually experienced. So we built monitoring that checks whether your site actually works — not just whether the server responds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://getsitewatch.com?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=uptime-is-broken" rel="noopener noreferrer"&gt;Sitewatch&lt;/a&gt; monitors asset integrity, MIME types, redirect chains, content fingerprints, and multi-region consistency. When something breaks, it classifies the root cause — infrastructure, application, or content delivery — so you know what to fix, not just that something is wrong.&lt;/p&gt;

&lt;p&gt;Free tier for 1 site. No credit card. Check it out if you've been burned by a green dashboard that was lying to you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Uptime was the right metric for a simpler web. The web isn't simple anymore. The monitoring needs to catch up.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>programming</category>
      <category>testing</category>
    </item>
    <item>
      <title>This is the story behind how Sitewatch was built — and why shipping faster with AI made production monitoring even more critical.</title>
      <dc:creator>Sitewatch</dc:creator>
      <pubDate>Fri, 27 Mar 2026 11:48:06 +0000</pubDate>
      <link>https://dev.to/sitewatch/this-is-the-story-behind-how-sitewatch-was-built-and-why-shipping-faster-with-ai-made-production-47c3</link>
      <guid>https://dev.to/sitewatch/this-is-the-story-behind-how-sitewatch-was-built-and-why-shipping-faster-with-ai-made-production-47c3</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nickycdk/ai-wrote-70-of-my-production-codebase-heres-why-that-actually-worked-2bmd" class="crayons-story__hidden-navigation-link"&gt;AI Wrote 70% of My Production Codebase. Here's Why That Actually Worked.&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nickycdk" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F806146%2Fd80b4677-6736-4840-85bc-3276cd0c6f78.jpeg" alt="nickycdk profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nickycdk" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Nicky Christensen
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Nicky Christensen
                
              
              &lt;div id="story-author-preview-content-3414372" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nickycdk" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F806146%2Fd80b4677-6736-4840-85bc-3276cd0c6f78.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Nicky Christensen&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nickycdk/ai-wrote-70-of-my-production-codebase-heres-why-that-actually-worked-2bmd" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 27&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nickycdk/ai-wrote-70-of-my-production-codebase-heres-why-that-actually-worked-2bmd" id="article-link-3414372"&gt;
          AI Wrote 70% of My Production Codebase. Here's Why That Actually Worked.
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/claudecode"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;claudecode&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nickycdk/ai-wrote-70-of-my-production-codebase-heres-why-that-actually-worked-2bmd" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;6&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nickycdk/ai-wrote-70-of-my-production-codebase-heres-why-that-actually-worked-2bmd#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              2&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Your CI Is Green. Your Site Might Not Be.</title>
      <dc:creator>Sitewatch</dc:creator>
      <pubDate>Fri, 27 Mar 2026 08:43:16 +0000</pubDate>
      <link>https://dev.to/sitewatch/your-ci-is-green-your-site-might-not-be-1gng</link>
      <guid>https://dev.to/sitewatch/your-ci-is-green-your-site-might-not-be-1gng</guid>
      <description>&lt;p&gt;Your CI is green. The deploy finished. Uptime is still 100%.&lt;/p&gt;

&lt;p&gt;And your users might still be staring at a blank page.&lt;/p&gt;

&lt;p&gt;CI tells you whether your build completed. It does not tell you whether production is serving the right content, with the right assets, from the right place.&lt;/p&gt;

&lt;p&gt;The failures that hurt most are rarely total outages. They're partial, silent, and easy to miss if all you track is status codes and deploy success.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with green dashboards
&lt;/h2&gt;

&lt;p&gt;A green deploy only proves your pipeline finished. It doesn't prove users can load your scripts, follow your redirects, or complete a flow. Production can be broken while every dashboard stays green.&lt;/p&gt;

&lt;p&gt;Here's what to actually verify — before your users find the problem for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Verify your asset hashes resolve
&lt;/h2&gt;

&lt;p&gt;After a deploy, your HTML references new hashed filenames — &lt;code&gt;main.c8d13.js&lt;/code&gt;, &lt;code&gt;styles.7fb2e.css&lt;/code&gt;. If your CDN is still serving the old HTML at some edge locations, those references point to files that no longer exist.&lt;/p&gt;

&lt;p&gt;The real problem is the mismatch between the HTML document and the assets it expects — and this is one of the most common silent failures after deploys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Fetch your page and extract script/stylesheet URLs&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://yoursite.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oP&lt;/span&gt; &lt;span class="s1"&gt;'(src|href)="[^"]*\.(js|css)"'&lt;/span&gt;

&lt;span class="c"&gt;# Then verify each asset returns 200&lt;/span&gt;
curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://yoursite.com/_next/static/chunks/main.c8d13.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check both the homepage and one or two critical routes — they can reference different bundles. You're looking for &lt;code&gt;200&lt;/code&gt; (not &lt;code&gt;404&lt;/code&gt;) and a fast response (not a timeout suggesting the file is missing behind a fallback).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When this matters most:&lt;/strong&gt; Platforms with CDN edge caching (Netlify, Vercel, Cloudflare Pages), especially when your HTML cache TTL outlives your deploy frequency.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Check MIME types on your JS and CSS
&lt;/h2&gt;

&lt;p&gt;A JavaScript file served with &lt;code&gt;Content-Type: text/html&lt;/code&gt; will be silently blocked by the browser. Your page loads. Your scripts don't execute. No visible error unless you inspect the network tab.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://yoursite.com/assets/app.js | &lt;span class="nb"&gt;grep &lt;/span&gt;content-type
&lt;span class="c"&gt;# Should be: content-type: application/javascript&lt;/span&gt;
&lt;span class="c"&gt;# Not: content-type: text/html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do this for your main JS bundle and your primary CSS file at minimum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When this matters most:&lt;/strong&gt; Sites behind Nginx with &lt;code&gt;try_files&lt;/code&gt; directives that fall back to &lt;code&gt;index.html&lt;/code&gt; for everything, or CDNs that replace 404s with branded HTML error pages.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Follow your redirect chains
&lt;/h2&gt;

&lt;p&gt;Don't just check that your URL responds — check where it &lt;em&gt;ends up&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sIL&lt;/span&gt; https://yoursite.com/important-page | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"(HTTP/|location:)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Look for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More than 3 redirects (excessive)&lt;/li&gt;
&lt;li&gt;A loop (same URL appearing twice)&lt;/li&gt;
&lt;li&gt;Landing on an unexpected destination (generic error page, wrong subdomain)&lt;/li&gt;
&lt;li&gt;Final status is not &lt;code&gt;200&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Protocol normalization fighting itself (HTTP → HTTPS → HTTP)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When this matters most:&lt;/strong&gt; After changing DNS, CDN rules, SSL configuration, or adding plugins/middleware that modify routing. Especially common when redirect rules at different layers (CDN, origin, application) don't agree with each other.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Verify from a different region
&lt;/h2&gt;

&lt;p&gt;Your site works from your location. That doesn't mean it works everywhere.&lt;/p&gt;

&lt;p&gt;CDN cache invalidation failures are regional — your local edge got the purge, Frankfurt didn't. Your users in Europe see stale HTML referencing deleted assets while your dashboard says green.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The real test is comparing actual responses from multiple locations. If you have a cloud instance in another region, run the same checks from there. A VPN works too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compare across regions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the HTML reference the same asset hashes?&lt;/li&gt;
&lt;li&gt;Do the same assets return &lt;code&gt;200&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Do redirect chains land on the same destination?&lt;/li&gt;
&lt;li&gt;Do cache headers (&lt;code&gt;age&lt;/code&gt;, &lt;code&gt;x-cache&lt;/code&gt;, &lt;code&gt;cf-cache-status&lt;/code&gt;) differ?
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Quick DNS sanity check from your machine&lt;/span&gt;
dig +short yoursite.com
&lt;span class="c"&gt;# Useful for catching post-migration drift, but not a substitute&lt;/span&gt;
&lt;span class="c"&gt;# for actually fetching content from multiple locations&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When this matters most:&lt;/strong&gt; After DNS changes, CDN migrations, cache purges, and any deploy to a platform with edge caching.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Verify the response contains actual content
&lt;/h2&gt;

&lt;p&gt;A page can return &lt;code&gt;200 OK&lt;/code&gt; with valid HTML and still be functionally empty — an app shell with no rendered content because the JavaScript that populates it never loaded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Look for a stable marker that only appears when the page rendered correctly — a page title, a heading, a product name, a form label, or CTA text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check for expected page title&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://yoursite.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;title&amp;gt;Your App Name"&lt;/span&gt;

&lt;span class="c"&gt;# Check for a content marker that should always exist&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://yoursite.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"Sign in"&lt;/span&gt;

&lt;span class="c"&gt;# Check the canonical tag as a secondary signal&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://yoursite.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'rel="canonical"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the response is just a &lt;code&gt;&amp;lt;div id="app"&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; and some &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags with none of your expected markers, your framework didn't render. Don't just check the homepage — verify one or two critical routes (checkout, login, pricing) as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When this matters most:&lt;/strong&gt; Any SPA or SSR framework (Next.js, Nuxt, Gatsby) where the HTML is a shell and JavaScript does the rendering. Also useful for catching branded CDN error pages that return &lt;code&gt;200&lt;/code&gt; with valid HTML — but not &lt;em&gt;your&lt;/em&gt; HTML.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Check your critical-path dependencies
&lt;/h2&gt;

&lt;p&gt;Your site is fine. Your payment provider's JS CDN is down. Your checkout is broken and you have no idea.&lt;/p&gt;

&lt;p&gt;Not every external script matters. Focus on the ones where failure means broken functionality, not just a missing pixel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Payment SDK&lt;/strong&gt; (Stripe, PayPal) — checkout dead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth provider&lt;/strong&gt; (Auth0, Firebase Auth) — login broken&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bot protection&lt;/strong&gt; (reCAPTCHA, Turnstile) — forms unsubmittable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature flags / config endpoint&lt;/strong&gt; — if your app boot depends on it, it's critical path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Extract external script sources&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://yoursite.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oP&lt;/span&gt; &lt;span class="s1"&gt;'src="https://[^"]*\.js"'&lt;/span&gt;

&lt;span class="c"&gt;# Verify each critical one responds with correct type&lt;/span&gt;
curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://js.stripe.com/v3/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When this matters most:&lt;/strong&gt; Always. Third-party failures happen independently of your deploys and are outside your control. But they break your users' experience just the same.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Compare content fingerprints
&lt;/h2&gt;

&lt;p&gt;The most subtle failures are when your site returns &lt;em&gt;different&lt;/em&gt; content than expected — a stale cached version, a CDN error page that returns &lt;code&gt;200&lt;/code&gt;, or content from a completely different environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate a hash of your page content&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://yoursite.com | &lt;span class="nb"&gt;sha256sum&lt;/span&gt;

&lt;span class="c"&gt;# Compare against a stored baseline&lt;/span&gt;
&lt;span class="c"&gt;# If the hash changed and you didn't deploy, something is wrong&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store the hash after a known-good deploy and compare on every subsequent check. Update your baseline after intentional deploys — otherwise every deploy looks like a content change. Combine this with the content markers from step 5 to distinguish between "content changed because we deployed" and "content changed because something broke."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When this matters most:&lt;/strong&gt; Between deploys. If the hash changes and nobody deployed, something is wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  A starting point script
&lt;/h2&gt;

&lt;p&gt;Here's a deliberately minimal version. In practice, you'll want route-specific checks, dynamic asset discovery, content markers per route, and multi-region validation. But this covers the basics as a CI post-deploy gate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;SITE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://yoursite.com"&lt;/span&gt;
&lt;span class="nv"&gt;FAILED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Running post-deploy checks for &lt;/span&gt;&lt;span class="nv"&gt;$SITE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Check main page responds&lt;/span&gt;
&lt;span class="nv"&gt;STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SITE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"200"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"FAIL: Main page returned &lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;FAILED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Check JS bundle MIME type&lt;/span&gt;
&lt;span class="nv"&gt;MIME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SITE&lt;/span&gt;&lt;span class="s2"&gt;/assets/app.js"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; content-type | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'\r'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MIME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s2"&gt;"javascript"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"FAIL: JS bundle MIME type is &lt;/span&gt;&lt;span class="nv"&gt;$MIME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;FAILED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Check redirect chain isn't excessive&lt;/span&gt;
&lt;span class="nv"&gt;REDIRECTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-sIL&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SITE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"HTTP/"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REDIRECTS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 4 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"WARN: &lt;/span&gt;&lt;span class="nv"&gt;$REDIRECTS&lt;/span&gt;&lt;span class="s2"&gt; redirects detected"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Check for expected content marker&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SITE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;title&amp;gt;Your App Name"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"FAIL: Expected content marker not found — page may not have rendered"&lt;/span&gt;
  &lt;span class="nv"&gt;FAILED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FAILED&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"All checks passed"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Issues detected — investigate before closing the deploy"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The problem with scripts
&lt;/h2&gt;

&lt;p&gt;A shell script is a good start. It catches the obvious stuff on deploy day.&lt;/p&gt;

&lt;p&gt;The problem is what happens after deploy day. Scripts run once. They check from one location. They break when your asset paths change. They don't catch the CDN edge that starts serving stale content three hours later, or the third-party SDK that goes down on Tuesday.&lt;/p&gt;

&lt;p&gt;Status codes are a terrible proxy for user experience. The real gap isn't "did this deploy work" — it's "is this site still working, right now, from everywhere."&lt;/p&gt;

&lt;p&gt;That's what we built &lt;a href="https://getsitewatch.com?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=post-deploy-checklist" rel="noopener noreferrer"&gt;Sitewatch&lt;/a&gt; for — continuous verification of everything in this checklist, from multiple regions, on every check cycle. Not just after deploys.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What does your post-deploy verification look like? If you've got a check I didn't cover, drop it in the comments — always looking to make the checklist more complete.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>monitoring</category>
      <category>startup</category>
    </item>
    <item>
      <title>The Most Dangerous Response Code Isn't 500. It's 200.</title>
      <dc:creator>Sitewatch</dc:creator>
      <pubDate>Thu, 26 Mar 2026 19:26:05 +0000</pubDate>
      <link>https://dev.to/sitewatch/the-most-dangerous-response-code-isnt-500-its-200-402n</link>
      <guid>https://dev.to/sitewatch/the-most-dangerous-response-code-isnt-500-its-200-402n</guid>
      <description>&lt;p&gt;A 500 error is loud. Your monitoring fires. Your on-call gets paged. Someone fixes it within the hour.&lt;/p&gt;

&lt;p&gt;A 200 that serves broken content is silent. Your monitoring dashboard stays green. Your users see a blank page, a broken checkout, a form that submits to nothing. And nobody on your team knows until a customer complains — or churns.&lt;/p&gt;

&lt;p&gt;Modern web stacks fail in ways a simple status code cannot describe. Here are six common patterns where &lt;code&gt;200 OK&lt;/code&gt; actively hides broken websites.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Phantom Deploy: Missing Bundle Hashes
&lt;/h2&gt;

&lt;p&gt;Modern frontend frameworks produce hashed filenames: &lt;code&gt;main.a4f2c.js&lt;/code&gt;, &lt;code&gt;styles.b7e91.css&lt;/code&gt;. On every deploy, these hashes change. The HTML document references the new filenames. The old files get cleaned up.&lt;/p&gt;

&lt;p&gt;Here's the failure mode:&lt;/p&gt;

&lt;p&gt;You deploy at 2pm. The build produces &lt;code&gt;vendor.c8d13.js&lt;/code&gt; and &lt;code&gt;app.7fb2e.css&lt;/code&gt;. Your CDN edge nodes in Frankfurt still serve the HTML from the previous build — which references &lt;code&gt;vendor.9a1b0.js&lt;/code&gt; and &lt;code&gt;app.3de4f.css&lt;/code&gt;. Those files are gone. The document loads fine. Every asset 404s. Users in Europe see a blank screen for 45 minutes until the CDN cache expires.&lt;/p&gt;

&lt;p&gt;This is especially common on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Netlify&lt;/strong&gt; with atomic deploys but CDN propagation delays&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt; with ISR pages serving stale HTML&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any CDN&lt;/strong&gt; where HTML cache TTLs are longer than deploy frequency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is cache invalidation discipline — but the point is that your uptime monitor never sees the problem. It fetches the document, gets 200, and moves on. It never checks whether &lt;code&gt;main.a4f2c.js&lt;/code&gt; actually exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Silent MIME Rejection
&lt;/h2&gt;

&lt;p&gt;Browsers enforce strict MIME type checking for scripts and stylesheets. If your server responds with &lt;code&gt;Content-Type: text/html&lt;/code&gt; for a JavaScript file, the browser silently blocks execution. No visible error. Your SPA just doesn't boot.&lt;/p&gt;

&lt;p&gt;How does a JS file end up served as HTML?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A CDN or reverse proxy intercepts the request and returns an error page (HTML) instead of the actual file&lt;/li&gt;
&lt;li&gt;A misconfigured Nginx &lt;code&gt;try_files&lt;/code&gt; directive falls through to &lt;code&gt;index.html&lt;/code&gt; for any path — including &lt;code&gt;.js&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;Cloudflare's custom error pages replace a 404 with a branded HTML error page, served as &lt;code&gt;text/html&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server logs show 200. The CDN metrics show successful delivery. The browser silently ignores the script. Your monitoring sees 200 and reports everything healthy. The only way to catch this is to verify the &lt;code&gt;Content-Type&lt;/code&gt; header against the expected MIME type for each critical asset.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Redirect Ouroboros
&lt;/h2&gt;

&lt;p&gt;A redirect loop is usually obvious — the browser shows &lt;code&gt;ERR_TOO_MANY_REDIRECTS&lt;/code&gt;. But most monitoring tools follow a limited number of redirects and report the final status, some don't follow redirects at all, and the loop might only trigger for specific user agents, cookie states, or geographic regions.&lt;/p&gt;

&lt;p&gt;Common causes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Shopify app redirects &lt;code&gt;/products&lt;/code&gt; to &lt;code&gt;/collections&lt;/code&gt;, while a theme redirect sends &lt;code&gt;/collections&lt;/code&gt; back to &lt;code&gt;/products&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Mixed HTTP/HTTPS configurations where each protocol redirects to the other&lt;/li&gt;
&lt;li&gt;CDN-level redirect rules conflicting with origin-level rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your monitoring sees the first 301 and considers that a valid response. Meanwhile, real browsers with real cookies hit an infinite loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Ghost Server
&lt;/h2&gt;

&lt;p&gt;After a DNS migration, CDN swap, or infrastructure change, your domain might resolve to the wrong server. The wrong server still &lt;em&gt;responds&lt;/em&gt; — it's just not &lt;em&gt;your&lt;/em&gt; server anymore.&lt;/p&gt;

&lt;p&gt;Scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You migrate from AWS to Vercel but a DNS record still points a subdomain to the old EC2 instance. The instance serves stale content from 6 months ago. 200 OK.&lt;/li&gt;
&lt;li&gt;A CDN failover activates and sends traffic to a backup origin that was never updated. It serves the version from the last time someone deployed to it. 200 OK.&lt;/li&gt;
&lt;li&gt;A load balancer health check passes because the server responds, but it's responding with a default page, not your application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The response is valid HTTP. The status is 200. The content is just... wrong. It's from a different version, a different environment, or a different application entirely.&lt;/p&gt;

&lt;p&gt;Catching this requires tracking the resolved IP and server identity over time — and comparing the content fingerprint against a known baseline. Not just "did it respond" but "did it respond with the right content from the right server."&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The API Gateway Mask
&lt;/h2&gt;

&lt;p&gt;API gateways, reverse proxies, and edge functions sit in front of your application. When the app behind them crashes, the gateway often catches the error and returns its own response — with a 200 status code.&lt;/p&gt;

&lt;p&gt;Your landing page URL returns &lt;code&gt;{"error":"unauthorized","statusCode":401}&lt;/code&gt; as a &lt;code&gt;200 OK&lt;/code&gt; with &lt;code&gt;Content-Type: application/json&lt;/code&gt;. Or your edge function crashes and the platform serves a generic fallback page. Valid HTML. 200 OK. Not your application.&lt;/p&gt;

&lt;p&gt;Common with AWS API Gateway fronting Lambda, Cloudflare Workers with fallback behavior, and Vercel edge middleware that wraps errors. If your monitoring doesn't verify that the response content type matches what you'd expect for that URL, these failures pass undetected.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The Third-Party Collapse
&lt;/h2&gt;

&lt;p&gt;Your site loads. Your HTML is correct. Your own assets are fine. But a critical external dependency is down — and your page breaks without your server knowing.&lt;/p&gt;

&lt;p&gt;This matters most when the dependency is load-bearing: Stripe's JS SDK failing means your checkout form renders as an empty div. An auth provider's script timing out means users can't log in. A CDN-hosted app bundle 404ing means your SPA doesn't boot. A bot-protection script blocking form submission means your lead capture is dead.&lt;/p&gt;

&lt;p&gt;Your server returns 200. Your monitoring checks your domain. The broken resource is on someone else's domain. That gap is where this failure hides.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Common Thread
&lt;/h2&gt;

&lt;p&gt;All six patterns share one characteristic: &lt;strong&gt;the HTTP status code says everything is fine while the user experience is broken.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Status codes tell you whether a request succeeded at the protocol level, not whether the page is actually usable. A 200 means "the server processed your request and returned a response." It doesn't mean the response contains what the user needs.&lt;/p&gt;

&lt;p&gt;A lot of monitoring still relies on a false assumption: that a successful HTTP response is a good proxy for a working site. For simple, server-rendered pages with no external dependencies, that was close enough. For modern web applications with hashed assets, CDN layers, API gateways, SPAs, and third-party dependencies, it's a dangerous gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Would Actually Catch These
&lt;/h2&gt;

&lt;p&gt;The common thread across all six patterns is that the status code is the wrong layer to monitor. Catching these failures means shifting from "did the server respond?" to "does the response actually constitute a working page?"&lt;/p&gt;

&lt;p&gt;That means treating the HTML document as a manifest, not a destination — following up on the assets it references, verifying their types, and confirming they load. It means following redirect chains to completion rather than trusting the first hop. It means tracking what your site returns over time so you notice when the content silently changes. And it means checking from more than one location, because CDN failures don't happen everywhere at once.&lt;/p&gt;

&lt;p&gt;This is the problem we built &lt;a href="https://getsitewatch.com?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=http200-dangerous" rel="noopener noreferrer"&gt;Sitewatch&lt;/a&gt; to solve. But regardless of what tool you use, the principle matters: stop trusting &lt;code&gt;200 OK&lt;/code&gt; as proof that your site works.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you been burned by a silent failure that hid behind 200 OK? I'd be curious to hear what the failure pattern was — these stories are how the industry gets better at catching them.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>softwaredevelopment</category>
      <category>saas</category>
    </item>
    <item>
      <title>Your Site Is Up. Your Site Is Broken. Both Are True.</title>
      <dc:creator>Sitewatch</dc:creator>
      <pubDate>Thu, 26 Mar 2026 13:52:15 +0000</pubDate>
      <link>https://dev.to/sitewatch/your-site-is-up-your-site-is-broken-both-are-true-2l11</link>
      <guid>https://dev.to/sitewatch/your-site-is-up-your-site-is-broken-both-are-true-2l11</guid>
      <description>&lt;p&gt;Every monitoring tool on the market will tell you when your site is down. None of them will tell you when your site returns HTTP 200 OK but serves a broken experience.&lt;/p&gt;

&lt;p&gt;We built Sitewatch to close that gap. This post covers the detection engineering behind it — what we check, how we confirm failures, and why traditional uptime monitoring has a fundamental blind spot.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: 200 OK Is Not "OK"
&lt;/h2&gt;

&lt;p&gt;A standard uptime monitor sends an HTTP request, gets a 200 status code, and marks your site as healthy. But a 200 response tells you almost nothing about whether your site actually works.&lt;/p&gt;

&lt;p&gt;Here are real failure patterns that return 200 OK:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Vercel deploy changes your bundle hash. &lt;code&gt;main.a4f2c.js&lt;/code&gt; now 404s, but the HTML document still returns 200. Your app shell loads. Nothing renders.&lt;/li&gt;
&lt;li&gt;Cloudflare serves &lt;code&gt;app.js&lt;/code&gt; with &lt;code&gt;Content-Type: text/html&lt;/code&gt; instead of &lt;code&gt;application/javascript&lt;/code&gt;. Chrome silently blocks execution. The page loads — with zero interactivity.&lt;/li&gt;
&lt;li&gt;A WordPress plugin misconfigures redirects. &lt;code&gt;/checkout&lt;/code&gt; redirects to &lt;code&gt;/checkout&lt;/code&gt; redirects to &lt;code&gt;/checkout&lt;/code&gt;. The browser gives up. Your uptime tool never follows the chain.&lt;/li&gt;
&lt;li&gt;After an AWS migration, your domain resolves to a decommissioned server. It serves stale content from six months ago. HTTP 200. Green dashboard.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these is invisible to a ping-based monitor. Every one of them is a real incident that affects users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Detection Model: 11 Rules Across 3 Categories
&lt;/h2&gt;

&lt;p&gt;We don't just check "is the server responding." We run 11 detection rules across three domains:&lt;/p&gt;

&lt;h3&gt;
  
  
  Asset Integrity
&lt;/h3&gt;

&lt;p&gt;This is where most silent failures live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ASSET_MISSING&lt;/strong&gt; — We check whether critical JS, CSS, and image assets return valid responses. After a deploy, bundle filenames often change (hashed filenames like &lt;code&gt;main.a4f2c.js&lt;/code&gt;). If the HTML references an asset that now 404s, 403s, or 5xxs, the page is broken even though the document loaded fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ASSET_MIME_MISMATCH&lt;/strong&gt; — We verify that the &lt;code&gt;Content-Type&lt;/code&gt; header matches what the browser expects. A JavaScript file served as &lt;code&gt;text/html&lt;/code&gt; will be silently blocked by the browser's MIME type checking. The page loads. The script never executes. No error in the server logs.&lt;/p&gt;

&lt;p&gt;We use HEAD requests to check status codes and MIME types — no browser overhead, no JavaScript execution cost. If HEAD fails or returns ambiguous results, we fall back to GET.&lt;/p&gt;

&lt;h3&gt;
  
  
  Routing &amp;amp; Resolution
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;REDIRECT_LOOP&lt;/strong&gt; — We follow redirect chains and detect circular patterns before &lt;code&gt;ERR_TOO_MANY_REDIRECTS&lt;/code&gt; kills the request. We log the full chain with status codes and final destination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HOST_DRIFT&lt;/strong&gt; — We track the resolved IP and server headers over time. If your domain suddenly resolves to a different origin — common after DNS migrations, CDN changes, or failover misconfigurations — we flag it and compare the content fingerprint against the known baseline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NON_HTML_PAGE&lt;/strong&gt; — If a URL that should serve a web page returns JSON (&lt;code&gt;{"error":"unauthorized"}&lt;/code&gt;), XML, or an empty body, we catch it. This happens more than you'd think with API gateways sitting in front of web apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Availability &amp;amp; Performance
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;UNAVAILABLE&lt;/strong&gt; — The classic check. 5xx responses, timeouts, connection refused. We still do this — it's just not the only thing we do.&lt;/p&gt;

&lt;p&gt;Plus broken link checking (up to 50 links per page), API endpoint monitoring, and response structure validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Confirmation: The 2-of-3 Retry Model
&lt;/h2&gt;

&lt;p&gt;False positives are the fastest way to lose trust in a monitoring tool. If your team gets woken up at 3am for a transient CDN glitch that resolved itself in 8 seconds, they stop trusting alerts. Then they miss the real ones.&lt;/p&gt;

&lt;p&gt;We use a &lt;strong&gt;2-of-3 retry confirmation&lt;/strong&gt; model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First check detects an issue.&lt;/li&gt;
&lt;li&gt;We immediately re-check twice more.&lt;/li&gt;
&lt;li&gt;The issue must fail on &lt;strong&gt;at least 2 of 3 consecutive checks&lt;/strong&gt; to become an incident.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A single transient failure — a momentary CDN hiccup, a network blip, a slow edge propagation — never triggers an alert. This eliminates the noise without adding dangerous delay. The entire confirmation cycle completes within the check interval.&lt;/p&gt;

&lt;p&gt;For multi-region checks, this runs &lt;strong&gt;independently per region&lt;/strong&gt;. A CDN edge failure in EU doesn't need to reproduce in US to become an incident — but it does need to reproduce within its own region.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fingerprinting: One Problem, One Incident
&lt;/h2&gt;

&lt;p&gt;When an asset breaks, every check that hits that asset will detect the same failure. Without deduplication, a broken JS bundle could generate dozens of alerts over a few hours.&lt;/p&gt;

&lt;p&gt;We generate a &lt;strong&gt;SHA-256 fingerprint&lt;/strong&gt; for each detected issue — combining the failure type, affected URL, and relevant metadata. If an active incident already exists with the same fingerprint, we don't create a new one. We also enforce a &lt;strong&gt;30-minute per-incident cooldown&lt;/strong&gt; on alert dispatches.&lt;/p&gt;

&lt;p&gt;The result: 12 consecutive checks detecting the same broken asset → 1 alert, not 12.&lt;/p&gt;

&lt;h2&gt;
  
  
  Root Cause Classification
&lt;/h2&gt;

&lt;p&gt;Detecting a failure is half the problem. The other half is telling you &lt;em&gt;why&lt;/em&gt; it broke.&lt;/p&gt;

&lt;p&gt;We classify every incident into one of &lt;strong&gt;10 cause families&lt;/strong&gt; across three domains:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure:&lt;/strong&gt; DNS resolution failure, origin server error, SSL/TLS certificate issue, network connectivity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application:&lt;/strong&gt; Deployment artifact missing, application error, configuration drift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content Delivery:&lt;/strong&gt; CDN cache misconfiguration, third-party dependency failure, redirect misconfiguration.&lt;/p&gt;

&lt;p&gt;Classification uses evidence from the check data — HTTP headers, response content, redirect behavior, MIME types, IP resolution, and fingerprint diffs. We assign a confidence score up to 90%. A high-confidence diagnosis (70%+) means the evidence strongly matches a known failure pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack-Aware Context
&lt;/h2&gt;

&lt;p&gt;Knowing &lt;em&gt;what&lt;/em&gt; broke and &lt;em&gt;why&lt;/em&gt; is useful. Knowing &lt;em&gt;how to fix it for your specific stack&lt;/em&gt; is what actually saves time.&lt;/p&gt;

&lt;p&gt;We detect 23 tech stacks by analyzing HTTP response headers (&lt;code&gt;X-Powered-By&lt;/code&gt;, &lt;code&gt;Server&lt;/code&gt;, &lt;code&gt;X-Vercel-Id&lt;/code&gt;), HTML meta tags, script patterns, and asset URL structures. Detection happens automatically with every check — no configuration required.&lt;/p&gt;

&lt;p&gt;When an incident fires, the fix guidance is tailored to your stack. A "deployment artifact missing" incident on Vercel gets different remediation steps than the same failure on Netlify or a self-hosted Nginx setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Region: Catching Regional Divergence
&lt;/h2&gt;

&lt;p&gt;CDN failures are often regional. Your site might work perfectly from US-East while EU users get stale cached assets from a failed purge.&lt;/p&gt;

&lt;p&gt;Multi-region checks run the full detection suite from multiple geographic points. Each region runs its own independent 2-of-3 retry confirmation. We compare results across regions to distinguish between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Global outage&lt;/strong&gt; — all regions failing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regional divergence&lt;/strong&gt; — specific edges serving different content, stale caches, or geo-routing sending traffic to wrong origins&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This catches CDN edge divergence, geo-routing misconfigurations, and regional failover failures — problems that are invisible if you only check from one location.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Don't Do
&lt;/h2&gt;

&lt;p&gt;We don't run a headless browser. We don't execute JavaScript. We don't render the page and take screenshots.&lt;/p&gt;

&lt;p&gt;This is a deliberate choice. Browser-based monitoring is slow, expensive, and fragile. It catches visual regressions — which matters — but it's a different problem from "your assets are broken and your site is non-functional."&lt;/p&gt;

&lt;p&gt;Our checks are HTTP-based. They're fast, cheap to run at high frequency, and they catch the structural failures that actually take sites down in production. Visual regression testing is complementary but separate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture in Summary
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Check cycle:
  1. Fetch document (GET)
  2. Verify status, content-type, body structure
  3. Extract and verify critical assets (HEAD requests)
  4. Follow redirect chains
  5. Compare fingerprints against baseline
  6. Run detection rules (11 rules)
  7. 2-of-3 retry on failures
  8. Classify root cause (10 families, up to 90% confidence)
  9. Match tech stack (23 stacks)
  10. Generate incident with evidence + fix guidance
  11. Deduplicate and dispatch alerts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of this runs every 5–30 minutes depending on plan, from multiple regions, without requiring any code changes or agent installation on your infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Sitewatch has a free tier — 1 site, 30-minute checks, no credit card. If you manage multiple sites and want to catch the failures your current monitoring misses, that's what we built it for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://getsitewatch.com?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=detection-engineering" rel="noopener noreferrer"&gt;getsitewatch.com&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you have questions about the detection approach or want to dig into specific failure patterns, drop a comment — happy to go deeper on any of this.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>devops</category>
      <category>webdev</category>
      <category>observability</category>
    </item>
  </channel>
</rss>
