Carlos A. Rojas

A javascript-free experience!

JavaScript Without the Dark Side

Progressive enhancement over mandatory scripting

Confirmed

The premise

An earlier article argued that mandatory client-side scripting carries real privacy and reliability costs: shadow profiling, silent failures, and bloat. The obvious objection is “so you just hate JavaScript.” Not quite. The useful distinction is between JavaScript as a requirement for basic function and JavaScript as an optional enhancement layered on top of something that already works. This site is proof the first is avoidable; this article is how.

Build the HTML baseline first

Progressive enhancement inverts the usual order. You build the document so that a browser with scripting disabled — or a script that failed to load on a flaky connection — still renders readable content and working navigation. Then, and only then, you consider whether a script would make a working thing nicer.

Concretely, the baseline means: real <a href> links (not onclick handlers), real <form> elements with a method and action, semantic headings, and server-rendered state. If the server can produce the final HTML, the client has nothing left to do.

Add JS only as enhancement

The litmus test: disable scripting and use the feature. If it still works, your JS is an enhancement. If it breaks, your JS is a dependency, and you have shipped the failure mode the previous article described. A collapsible menu that is a plain nested list without JS is an enhancement. A menu that is an empty <div> until a bundle hydrates it is a dependency.

Server-rendered patterns that replace JS

Filtering / tabs. A query string plus server-side branching. The topic filters on the Articles and Labs indexes here are exactly this: each tag is an <a> to ?topic=Privacy, and the server renders the filtered list. No JS, bookmarkable, and it works in a text browser.

// Server-rendered filter (mirrors this site's own index pages)
$topic = $_GET['topic'] ?? null;        // read query string
foreach ($entries as $e) {
    if ($topic !== null && !in_array($topic, $e['topics'])) continue;
    echo renderCard($e);                // render only matches
}

Expand / collapse. The HTML <details> and <summary> elements give you native accordions with zero script.

<details>
  <summary>Show the full command</summary>
  <pre><code>rsync -avhP ...</code></pre>
</details>

Form validation. HTML5 required, type=email, pattern, and min/max validate client-side with no script — but you still validate again on the server, because client checks are a convenience, never a control.

When JS is genuinely the right call

Being principled is not being dogmatic. Rich text editors, live canvas/graphics, real-time collaboration, and audio/video manipulation are legitimately interactive and have no server-only equivalent. The honest rule is a cost/benefit one: does this feature require interactivity that HTML and CSS cannot express? If yes, scope the script tightly, load it only on the page that needs it, and keep a usable fallback for the surrounding content. The failure of the modern web is not that JavaScript exists; it is that a static article refuses to render without a megabyte of it.

Takeaway

Treat JavaScript like any other dependency: justify it, scope it, and make sure the system degrades gracefully without it. “Works without JS” is not a nostalgic constraint — it is a reliability, accessibility, and privacy property you get almost for free if you build in the right order. This whole site is the proof: every page you are reading was rendered on the server and sent to you finished.

Update 2024-01-20: Initial publication as the constructive follow-up to the dark-side article.