Script Shell Pages: When Your App Loads But Nothing Works
A deploy goes out at 2 PM. At 2:03 PM, your homepage is returning 200 OK with a 4.2 KB HTML payload. No alerts fire. By 6 PM, bounce rate has spiked, conversions are near zero, and Googlebot is fetching pages with no indexable content. The UI looks fine locally. In production, the app loads — and renders nothing.
What a script shell looks like:
200 OK
HTTP status
4.2 KB
HTML size
<100
visible chars
✓
all monitors green
This is the default failure mode of modern SPAs. It passes every system check.
On This Page
What's Actually Happening
A script shell page is HTML that contains JavaScript bootstrapping code but no usable content. Every SPA framework ships this by default — Vite, CRA, Lovable, most React setups.
A typical production response looks like this:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Your App</title>
<link rel="stylesheet" href="/assets/index-9f3a1.css">
</head>
<body>
<div id="root"></div>
<script type="module" src="/assets/index-9f3a1.js"></script>
</body>
</html>That response is often:
- • 2–6 KB total
- • Under 100 characters of visible text
- • Zero semantic elements (no
<h1>,<article>,<nav>) - • A single root
divwith no children
Everything depends on JavaScript executing correctly. There is no fallback. If the bundle 404s, if hydration throws, if the API returns 500 — users get a blank page and bots get an empty document.
Browser vs HTML Reality
The disconnect between what your team sees and what bots see is the entire problem.
What the browser shows
- • Full UI with navigation
- • Product data, pricing, testimonials
- • Working CTAs and forms
- • ~35 KB rendered DOM, 1,200+ words
What the HTML actually contains
- • An empty container
<div id="root"> - • Script and stylesheet references
- • No headings, no paragraphs, no links
- • ~4 KB total, <100 visible chars
Both are the same page. Both return 200. One is the page you built. The other is what every bot, every crawler, and every user with a failed JS load actually receives.
Why It Looks Healthy
Every infrastructure signal you have will tell you the page is fine. None of them validate content.
The system is healthy. The page is unusable. This is a page-level failure mode that infrastructure monitoring is structurally incapable of catching.
Why Tools Miss This
Most teams monitor infrastructure, not output. Every standard tool has the same blind spot.
| Tool | What It Checks | Catches Empty HTML? |
|---|---|---|
| Uptime monitoring | Confirms 200 OK, ignores body | No |
| Server logs | Backend behavior only | No |
| Synthetic tests | Often stop at DOM ready | No |
| Lighthouse | Runs with JS enabled and cached assets | No |
| Error tracking | Thrown JS exceptions | No |
| HTML output validation | Raw HTML size, content, structure | Yes |
| Bot-perspective fetch | What crawlers actually receive | Yes |
Your system can be 100% healthy by every standard metric while every page is unusable. None of these tools answer the question that actually matters: "Does this page contain content?"
What We See in Production
We see this constantly, and it's almost always tied to deploys. Three patterns account for nearly every case.
Missing JS bundle (most common)
A new build references /assets/index-9f3a1.js. The CDN still serves old assets at the edge. The file returns 404.
Result: HTML loads (4 KB), script fails to load, page never renders. Users see a blank screen. Bots see nothing. Revenue stops immediately.
GET / 200 OK 4.2 KB
GET /assets/index-9f3a1.js 404 Not Found
// Page never mounts. No error in your logs.API-first rendering failure
The app waits for /api/page-data before rendering anything. The API returns 500 for ~5% of requests due to a downstream timeout.
Result: HTML loads, JS runs but never mounts content, page stays empty. This shows up as intermittent "ghost outages" — some users see content, others don't. Support gets reports you can't reproduce.
Hydration crash after deploy
Server and client output diverge. React throws during hydration with "Text content does not match server-rendered HTML." Rendering halts.
Result: Partial UI or full blank, no recovery, still returns 200. See our companion post on hydration crashes for the full breakdown.
All three ship to production without alerts. All three return 200 OK. All three turn your site into a 4 KB empty response.
How to Detect It
Stop guessing. Check the output directly.
1. Fetch raw HTML
Pull the page without executing JS. If your homepage HTML is 3–5 KB with no readable text, it's broken.
curl -s https://yoursite.com | wc -c
# Under 5KB → script shell
curl -s https://yoursite.com | grep -oP '(?<=>)[^<]+' | wc -w
# Under 100 words → bots see nothingYou should see headings, paragraphs, and links in that output. If you don't, bots won't either.
2. Compare source vs rendered
Open the page in a browser and copy a chunk of visible text. Then view source and search for it. If it's missing, your content is JS-only.
This is the simplest test that exists for content delivery, and almost no team runs it after every deploy.
3. Check HTML structure
Red flags in the raw HTML:
- •
<body>contains only one<div> - • No
<h1>,<article>, or<main> - • No meaningful text nodes
- • All content references resolve via JS at runtime
4. Force failure conditions
Block JS in DevTools or simulate a failed script load. If the page becomes blank, you've confirmed the dependency. Now you know what 5% of your users (and 100% of your bots) are seeing.
Solutions
There's no single fix. The problem spans rendering strategy, monitoring, and deploy hygiene.
Put content in the HTML
If your content only exists after JS runs, it is fragile by design. At minimum:
- • Core text content must exist in the HTML response
- • Navigation links must be present
- • Page structure (headings, semantic tags) must be complete
This is what DataJelly Edge does for SPAs that can't migrate to SSR — it generates real HTML for bots without rewriting the app.
Validate HTML, not behavior
Stop relying on "it works in the browser." Validate:
- • HTML size (content pages should not be single-digit KB)
- • Text presence (raw, not rendered)
- • Structural elements (h1, main, nav, links)
Monitor real pages
You need checks that fail when content disappears. Not "is the server up?" but:
- • "Does this page contain 500+ words of text?"
- • "Does the
<h1>exist?" - • "Did HTML size drop from 35 KB to 4 KB between deploys?"
That's the difference between catching a script shell regression and shipping it.
DataJelly Guard does exactly this
Guard inspects real HTML output, flags content loss, and catches script shell regressions before they hit users. Built for React, Vite, and Lovable apps where this failure mode is the default.
Practical Checklist
Run this after every deploy. If any of these fail, investigate immediately — regardless of what your status dashboard says.
HTML response > 15 KB for content pages
Single-digit KB is a script shell
Visible text present in raw HTML
Not just JS-injected — actually in the response body
h1, main, and links exist in source
Bots index structure, not your rendered DOM
No dependency on API calls for initial render
If the API hiccups, the page should still show content
JS bundles return 200, not 404
Verify all asset hashes after deploy
Page still shows content with JS disabled
Open DevTools, disable JS, reload. What do you see?
No sudden HTML size drops between deploys
40 KB → 4 KB means something broke
Hydration completes without console warnings
Even one warning can kill an entire subtree
Run These Tests Now
Guard automates all of this. Until it ships, run these manually — no signup required.
Each tool below tests a different layer of the script shell problem. If your page returns 200 but the HTML is empty, these will show you exactly where it breaks.
Page Validator
Check whether your HTML contains the headings, links, and content bots need.
HTTP Debug
Inspect raw HTTP responses by user agent — see what Googlebot actually receives.
Visibility Test
Compare what bots see vs what users see — the core script shell check, done manually.
Page Speed Analyzer
Check Core Web Vitals and rendering performance that uptime tools miss.
Robots.txt Tester
Verify crawlers aren't blocked from the pages you think are live.
The takeaway
Script shell pages are not rare bugs. They are the default failure mode of SPAs. A single missing script or failed API turns your site into a 4 KB empty response that still returns 200 OK.
Your monitoring will say everything is fine. Your users and search traffic will say otherwise. If you're not validating HTML output directly, you are shipping blind.
Interested in Guard?
Guard is launching soon. If your React, Vite, or Lovable app has ever shipped a 4 KB shell to production without anyone noticing, we built this for you.