Graceful degradation when JavaScript fails
Core content and primary navigation should keep working when JavaScript fails to load or throws — treat client-side scripts as an enhancement, not the delivery mechanism.
What it is
Graceful degradation means the page’s core content and primary navigation keep working when JavaScript fails — not only when it is switched off, but when a script times out, a CDN returns a 404 for the bundle, an exception throws halfway through execution, or an extension blocks it. The baseline is delivered in HTML and CSS; JavaScript is layered on top, and its failure costs a single feature, not the whole page.
This is the runtime companion to server-side rendering. SSR asks whether the content is in the server’s response at all; graceful degradation asks whether the page still works after that response arrives but the scripts do not.
Why it matters
Script delivery fails more often than people assume — a dropped packet on a flaky mobile connection, a request that raced a deploy, a corporate proxy, an ad blocker with an over-broad rule. A page that only renders its article body, opens its menu, or submits its forms after JavaScript runs turns every one of those failures into a blank screen. The content was already in the HTML; one failed request hid it.
The cost also lands on the long tail of non-rendering clients — feed readers, link-preview scrapers, and many AI crawlers — for which “JavaScript required” is indistinguishable from “broken”.
How to implement
- Deliver core content and navigation in the initial HTML. Use real
<a href>links and real<form action>so both function before any script runs. - Treat client behaviour as enhancement: attach it to markup that already works, and feature-detect before using it.
- Never gate visible content behind a successful
fetch. If a script reveals or hydrates content, ensure that content is present and readable without it. - Use
<noscript>as a backstop for the no-JavaScript path — but remember it does not fire when JavaScript loads and then errors, so it cannot be the whole strategy. - Keep menus, accordions, and dialogs operable with native elements (
<details>,<dialog>) or CSS where you can.
This site is a worked example: every spec page, the checklist, and all navigation render and work with JavaScript fully disabled. Only the ⌘K search overlay needs JavaScript, and the same search is reachable as a plain page at /search/.
Common mistakes
- Rendering the whole page into an empty
<div id="root">, so a script failure yields a white screen. - Links and buttons built from
<div>s wired up by JavaScript — dead if the script never runs (see empty links and buttons). - Relying on
<noscript>alone, which does nothing when a script loads but then throws. - Hiding content with CSS and revealing it with JavaScript, leaving it permanently hidden after an error.
Verification
- Disable JavaScript in DevTools and reload: core content and primary navigation must still be present and usable.
- In DevTools → Network, block the JS bundle’s URL and reload — the page should still work.
- Throttle to slow 3G and read the page before scripts finish; it should be legible mid-load.
curl https://example.com/and confirm the main content is in the returned HTML, not just a<script>tag.
Related topics
Sources & further reading
- WHATWG HTML Standard — The noscript element — WHATWG
- MDN — Progressive enhancement — MDN
- MDN — Graceful degradation — MDN
- MDN — <noscript> — MDN