A static site does not need JavaScript to be defensible; the absence of a <script> tag can itself be the point. This essay argues the engineering case for shipping a site whose script lanes are closed by policy, then proves the claim with the header this publication actually serves.
The argument has three parts: the threat surface you delete, the contract that makes deletion observable, and the cases where deleting the layer is the wrong call.
The threat surface you delete by not having a script lane
A page that executes no own or third-party script removes an entire class of failure and abuse at once. Cross-site scripting lives in a document that can run injected code; a document that cannot run code at all has nothing for an injection to escalate into. Third-party trackers and widgets are gone because there is no script tag to embed them. Supply-chain risk from accidental dependency drift in the browser bundle is gone because there is no bundle. Each of these is a bounded, nameable surface — the small-systems property of choosing boundaries you can articulate.
None of this claims the site is “secure”. It claims the attack surface is narrower and, crucially, easier to reason about because it is enumerated by construction rather than by audit.
The header that makes the boundary enforceable
A taste is not a policy. The difference here is that the no-script contract is enforced by the server, not by developer convention. The live response of this site carries:
content-security-policy: default-src 'self'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'; object-src 'none'; script-src 'none'; style-src 'self'; img-src 'self' data:
The script-src 'none' directive is the enforceable part. Even if a future release lets a stray <script> into a page, the browser refuses to execute it. The absence of JavaScript is therefore a served contract, checked by the verifier on every build (script-free), not a hope. This is the difference between saying “we do not use JS” and “the public edge refuses to run JS”.
What the static structure buys after the removal
With no scripting, the content model reverts to something closer to published documents. Caching is simpler because a static file has a stable byte identity and an ETag rather than a mutable DOM. Reading the served HTML in a terminal, using view-source, or indexing the tree is straightforward because the artifact is ordinary files on disk. The trade the site accepts is deliberate: no interactive state, no client-side rendering, no feedback that needs a view model. For a documentation and operations site that is the correct exchange, and it is worth writing down.
When the zero-JS claim is the wrong call
The boundary is a feature, not a badge of purity. A site that genuinely needs client-side interactivity — a realtime dashboard, a document editor, a payment flow — should not contort itself into serving static HTML and then bolting on a workaround. The wrong application of this decision is to shave a product to fit the constraint. The right application is to recognize that content that is read, searched, and archived profits from being a document, and to reserve scripting for where a document is insufficient.
For this property, the served header is the evidence, and it is reproducible on any release by reading the public edge. That is the difference between a preference and a deployed decision.
Sources
- MDN: Content Security Policy (
script-src), directives that disable script execution, checked 2026-08-13. - PortSwigger: What is cross-site scripting (XSS)?, the attack class removed when scripts cannot run, checked 2026-08-13.
- OWASP: Content Security Policy, CSP as a defense-in-depth control, checked 2026-08-13.