eovinicius

Notes · · 2 min read

How this site works

Plain HTML, CSS and JavaScript, a short build and a CSP without 'unsafe-inline'. The decisions behind eovinicius.dev.

This site doesn't use a framework. It's HTML, CSS and JavaScript files that run straight in the browser, plus a small build script that only exists for deployment. Here are the pieces and how they fit together.

The source runs without a build

In development, npm run dev starts a Node server that renders each page on every request. There's no bundler and no cache: I save the file and reload the page. The scripts are classic ones, loaded with defer in a fixed order, and the data ones (config.js, dev-content.js, i18n.js) only declare a global constant.

That choice has a useful side effect: the same data file serves both the browser and Node. The build reads dev-content.js with node:vm and writes the Dev page as plain HTML, for search engines and link previews that don't run JavaScript. When the script loads, it swaps that version for the interactive one.

The build only optimizes

build.mjs does a few things:

  • joins the CSS files into one inline, minified <style>, so nothing blocks rendering;
  • minifies each script with esbuild and adds a hash to its name, which allows a one-year immutable cache;
  • minifies the HTML;
  • builds the English version from the same index.html;
  • turns the Markdown notes, like this one, into pages;
  • computes the Content Security Policy.

A CSP without unsafe-inline

After minification, the build computes the SHA-256 of every inline <script> and <style> and puts those hashes in a CSP inside a <meta> tag. Any other inline script is blocked by the browser.

The price is one rule to remember: no style attribute in the HTML or in innerHTML strings. When I need a CSS variable per element, I use el.style.setProperty() in JavaScript, which the CSP allows.

Two languages, one HTML

The English version isn't a separate file. A list of pairs, with the Portuguese text and the English text, is applied to index.html at build time. If an entry isn't found, the build fails instead of publishing a half-translated page.

Tests that catch what matters

The tests are Playwright smoke tests against the production build, on a desktop and on an emulated phone. They check that the pages open in both languages, that the terminal responds, that the no-JavaScript HTML has the content and that no error shows up in the console. That last one covers the CSP: if anything gets blocked, the test fails.

In CI, Lighthouse requires a minimum score of 90 for performance and best practices, and 95 for accessibility and SEO.

The takeaway

Start without a framework and only add tools when a problem shows up. Make the build fail loudly when an assumption breaks. And test the artifact that goes to production, not the development setup.