Critical JavaScript Failures: When One Script Breaks Your Site
Homepage returns 200. Deploy is green. Every uptime monitor is happy. Checkout revenue drops to zero. We see this all the time — one failed script prevents the app from mounting and the entire page goes dark, while everything "upstream" looks fine.
Actual page snapshot from a failed deploy:
6.2 KB
HTML size
58
visible words
17
scripts requested
1
404'd chunk
Result: blank page. Status: 200 OK. No alerts fired.
On This Page
The Real Failure
A typical failing page in production looks like this:
HTML size: 6.2 KB
Visible text: 58 words
Scripts: 17 requested
Failed chunk: main.abc123.js → 404
Body: <div id="root"></div>
Status code: 200 OKThat page is dead. Users see nothing. Crawlers see nothing. Conversion stops.
The HTTP layer says success. The CDN says success. The API gateway says success. The page does not work.
What's Actually Happening
Every modern SPA — React, Vite, Lovable, Next.js client routes — ships a thin HTML shell and depends on JavaScript to render content. The normal sequence:
1. HTML loads (often <10KB)
2. JS bundles load (vendor, main, route chunks)
3. App mounts into <div id="root">
4. Content appearsBreak any step in step 2 and step 3 never happens. The root stays empty. There are no headings, no paragraphs, no buttons. The browser is technically "loaded" — it just has nothing to show.
What the crawler (and any user with a slow JS run) sees in the raw HTML:
<div id="root"></div>
<script src="/assets/vendor.js"></script>
<script src="/assets/main.abc123.js"></script> ← 404
<script src="/assets/router.js"></script>Concrete signals that fire on this failure:
html_size: 5–10KBvisible_text_length: <100word_count: <60resource_error_count: spikes from 0
Guard classifies this as critical_bundle_failure, js_crash, or blank_page. This is the default failure mode of JS-heavy apps, not an edge case.
Why Everything Looks Healthy
Every system check passes because nothing failed at the network layer.
The failure happens in the browser, after delivery. JS throws, the app never mounts, content never exists. Your monitoring validates delivery. It does not validate rendering. That's the gap.
Why Tools Miss This
Uptime tools check status codes, latency, and availability. They do not check the DOM. They do not measure visible text. They do not know whether the app mounted.
Frontend tests miss it for different reasons:
- They retry failed requests, hiding intermittent bundle failures
- They run in ideal conditions with warm caches
- They don't measure content loss — just whether selectors eventually appear
- Lighthouse runs a real browser with a generous render budget. Real users and crawlers don't.
This is a page failure, not an infrastructure failure. That's why it slips through every layer of "is the site up?" tooling.
What We See in Production
Three patterns account for almost every critical JS failure we see.
Checkout blocked by Stripe.js
This breaks in production when Stripe (or any third-party SDK in the render path) fails to load — DNS issue, regional outage, ad blocker, slow connection.
HTML size: 7.9 KB
Visible text: 110 words
Stripe.js: timeout (30s)
Result: Payment form never rendersNo alerts fire. The page is "loaded." Users hit the checkout step, see nothing, leave. Revenue drops immediately. We've watched teams lose six-figure days to this and only notice from a finance dashboard the next morning.
Bundle mismatch after deploy
This breaks in production when cached HTML references chunks that no longer exist. Common after content-hashed builds: HTML is cached at the edge for 5 minutes, but the new build invalidated the JS.
Before deploy:
visible_text_length: 820
resource_error_count: 0
word_count: 340
After deploy (cached HTML, new chunks):
visible_text_length: 45
resource_error_count: 6
word_count: 18
HTML unchanged. Page blank.Full blank page until the HTML cache expires. Most monitors never look at this.
Hydration crash
This breaks in production when client and server state diverge — feature flags, user-specific data, or a mismatched library version. The page renders, then JS throws on hydration, and the app never wires up.
HTML size: 42 KB
Word count: 900
Console: "Hydration failed because..."
Result: Static shell visible, every button deadCrawlers extract partial content. Users see a UI that doesn't respond. We covered this failure mode in depth in Hydration Crashes: The Silent Killer.
Guard detects this via console error tracking and resource failure spikes — not just status codes.
How to Detect It
You can verify any of this in seconds. There's no excuse for not knowing.
1. Inspect the raw HTML
curl -sL https://yoursite.com/ | wc -c
# < 10000 → red flag
curl -sL https://yoursite.com/ | grep -oE '<script' | wc -l
# > 10 with low text → script shell2. Compare source vs rendered
View Source vs DevTools Elements panel. If your headings, paragraphs, and CTAs only exist in the rendered DOM, your content is not reliably visible.
3. Track content deltas across deploys
Hard failure signals:
visible_text_lengthdrops >50%word_countcollapses (e.g. 900 → <100)html_sizedrops >40% with no intentional change
4. Monitor resource errors and console output
Bundle 404s, CDN timeouts, console exceptions. If resource_error_count moves off zero, rendering is compromised. Treat it as a P1 signal, not a graph to admire.
Real Thresholds
Use these as alerting defaults. They're not opinion — they're what we see correlate with broken pages across thousands of scans.
| Signal | Healthy | Suspect | Broken |
|---|---|---|---|
| html_size | > 20 KB | 10–20 KB | < 10 KB |
| visible_text_length | > 1,000 | 200–1,000 | < 200 |
| word_count | > 300 | 60–300 | < 60 |
| resource_error_count | 0 | 1–2 | ≥ 3 |
| h1 present | Yes | — | No |
| Render delta vs prior deploy | < 10% | 10–40% | > 40% |
If two or more land in "Broken" simultaneously, the page is not serving content. Period.
Solutions
Remove single-point failures
Do not gate your render on Stripe, analytics, auth providers, or experiment SDKs. Render core UI first, attach third-party functionality after mount.
Make HTML carry content
Critical text — title, H1, hero copy, CTA — must exist in the HTML response, not after JS execution. Prerender, SSR, or edge snapshots are all valid paths.
Isolate third-party scripts
Load with async or defer. Never block app initialization on a script you don't control.
Monitor output, not requests
Measure rendered text, DOM size, and resource errors. Status codes and response times do not tell you whether the page worked.
Guard does the last one for you — it monitors actual page output, flags content regressions across deploys, and surfaces bundle failures before traffic notices.
Practical Checklist
Run These Tests Now
Don't take our word for it. Check your own site in under a minute.
Quick Test: What Do Bots Actually See?
Most people guess. Don't.
Run this test and look at the actual response your site returns to bots.
Fetch your page as Googlebot
Use your terminal:
curl -A "Googlebot" https://yourdomain.comLook for:
- Real visible text (not just
<div id="root">) - Meaningful content in the HTML
- Page size (should not be tiny)
Compare bot vs browser
Now test what a real browser gets:
curl -A "Mozilla/5.0" https://yourdomain.comIf these responses are different, Google is indexing a different page than your users see.
Stop guessing — measure it.
Real example: 253 words vs 13,547
We see this constantly. Here's a real example from production: Googlebot saw 253 words and 2 KB of HTML. A browser saw 13,547 words and 77.5 KB. Same URL — completely different content.

If your HTML doesn't contain the content, Google doesn't either.
Compare Googlebot vs browser on your site → HTTP Debug ToolCheck for common failure signals
We see this all the time in production:
- HTML under ~1KB → usually empty shell
- Visible text under ~200 characters → thin or missing content
- Missing <title> or <h1> → weak or broken page
- Large difference between bot vs browser HTML → rendering issue
Use the DataJelly Visibility Test (Recommended)
You can run this without touching curl. It shows you:
- Raw HTML returned to bots (Googlebot, Bing, GPTBot, etc.)
- Fully rendered browser version
- Side-by-side differences in word count, HTML size, links, and content
What this test tells you (no guessing)
After running this, you'll know:
- Whether your HTML is actually indexable
- Whether bots are seeing partial content
- Whether rendering is breaking in production
This is the difference between "I think SEO is set up" and "I know what Google is indexing."
If you don't understand why this happens, read: Why Google Can't See Your SPA
If this test fails
You have three real options:
SSR
Works if you can keep it stable in production
Prerendering
Breaks with dynamic content and scale
Edge Rendering
Reflects real production output without app changes
If you do nothing, you will not rank consistently. Learn how Edge Rendering works →
This issue doesn't show up in Lighthouse. It shows up in rankings.
The takeaway
Modern JS apps fail quietly. One broken script can remove all content, break checkout, and pass every health check. If you don't measure rendered output, you'll find out from conversion loss, traffic drops, or user complaints. That's already too late.
DataJelly Guard monitors actual page output — DOM, visible text, resource errors, console exceptions. It detects blank pages, bundle failures, and content regressions across deploys. Built for React, Vite, and Lovable apps that fail this way.