Progressive enhancement still matters, even in a JavaScript world
Progressive enhancement is the idea that you build on a baseline that works without JavaScript — real HTML, real forms, real links — and then layer richness on top for browsers that can take it. In an era where everything is a JavaScript app, it sounds quaint. It stops sounding quaint the first time a script fails to load on a flaky network, a CDN has a bad day, a browser extension breaks your bundle, or a crawler that does not run JS visits your page. In every one of those cases, the app that had a working HTML baseline degrades gracefully, and the app that assumed JavaScript shows a blank screen. Enhancement is insurance you only notice when the JavaScript isn’t there.
Start with HTML that actually works
The baseline is not a fallback you add later — it is what you build first. A form that submits to a server endpoint works with zero JavaScript; a link that points to a real URL navigates without a router. Build those, then enhance:
<!-- works with no JS: real action, real method, real inputs -->
<form action="/search" method="GET">
<label for="q">Search</label>
<input id="q" name="q" type="search">
<button type="submit">Search</button>
</form>
Submit that with JavaScript disabled and it still searches, because the browser knows how to submit a form. The baseline is free — it is just using the platform as intended.
Enhance without breaking the base
Then JavaScript intercepts and improves — debounced live results, no full reload — while leaving the working form underneath. The enhancement hijacks the submit only when it can, so if the script never runs, the native submit still fires:
// enhance the working form; if this never runs, the plain form still submits
form.addEventListener("submit", (e) => {
if (!window.fetch) return; // no capability? let the native submit happen
e.preventDefault(); // capable: take over for a nicer experience
liveSearch(new FormData(form).get("q"));
});
The key discipline: JavaScript adds to a working thing, it does not replace a broken one. That is the difference between graceful degradation and a blank page.
The pragmatic middle: SSR is enhancement’s modern form
You do not have to make a rich SPA fully work without JavaScript to get most of the
benefit — the modern, pragmatic version of progressive enhancement is server
rendering. Ship real HTML content for the first paint (so crawlers, slow devices, and
a failed hydration all get something meaningful), then hydrate to the rich app. For
core flows that must be robust — sign-in, checkout, search — invest in a genuine
no-JS-capable baseline; for the deeply interactive parts, SSR-plus-hydration is the
sensible compromise. Either way the principle holds: never let a single failed script
be the difference between a working page and a blank one. The form-field-molecule and
responsive-image-set exercises both build on platform primitives (the form element,
the img element with srcset) that already work without JavaScript — the baseline you
enhance from rather than replace.