How this site is built
Fifteen static pages, no framework, no runtime dependencies, and nothing loaded from anyone else's server. Here is what that costs, what it buys, and the things I got wrong along the way.
No framework, and that is a decision rather than a gap
This site is a set of long-form case studies. Its job is to load quickly, read well, and still work in five years. None of that needs a framework, so it does not have one.
I want to be straight about the tradeoff, because "no framework" is easy to say and easy to get wrong. What I give up is real: no components, no reactive state, no ecosystem to lean on. If this site had accounts, a dashboard, or anything genuinely stateful, that would be the wrong call and I would reach for React without hesitating.
What it buys is a site any developer can open in a text editor and understand in a minute, that has no dependency tree to patch, and that ships 44 KB of my own code. A React baseline is larger than that before you write a line.
The build
One problem was worth solving with tooling
Fifteen pages were each carrying a hand-copied header, footer and icon set, and they had drifted. Nine pages were missing a navigation item. Sixteen links pointed at an anchor that did not exist on any page. That is exactly the failure duplication produces, and no amount of care fixes it by hand.
So there is a build step. It is about 150 lines of Node with no dependencies, and it does two things: it expands marked regions in each page from a single partial, and it concatenates the CSS files into one bundle. Then it stops.
The build has two properties I care about more than what it does. It is idempotent, so running it twice produces identical output and generated files diff cleanly. And its output is committed, so a plain clone of this repository opens and renders with no install and no build. The build helps me edit the site. It is not required for the site to work.
That second property is the one people skip, and it is the whole reason a static site is worth keeping static. A separate check verifies the committed output still matches its sources, and the deploy fails if it does not, so the convenience never quietly becomes a liability.
Dependencies
Removing the last things I did not control
The site used to load fonts from Google and icons from a CDN. Both worked. Both also meant that a page could not finish rendering until two servers I do not own decided to answer.
Fonts are now self-hosted variable files, 85 KB for the Latin subsets, covering every weight from two files. That also fixed a bug I had not noticed: three pages requested different font weights, and none of them requested the heaviest two that the headings actually use, so my largest headings were being faked by the browser rather than drawn properly.
Icons were about 250 KB of Font Awesome, loaded from a CDN, in two different versions on different pages, to draw around forty small shapes. They are now a single inline sprite of roughly 3 KB, built into each page. They take their colour and size from the text beside them, so they need no per-icon fiddling.
With nothing third-party left, the Content-Security-Policy could stop being advisory. It is enforced, with no CDN allow-listing, no inline scripts and no inline styles anywhere on the site.
Weight
The page weight was itself a bad work sample
A site about web craft was shipping a 12.5 MB homepage, and one case study at 23 MB. A single screenshot was 5.7 MB to fill a space the size of a postcard. On mobile data that is ten to twenty seconds of staring at nothing.
- Homepage: 12.5 MB and 38 requests, now 1.05 MB and 17 requests
- Heaviest case study: 23.0 MB, now 0.37 MB
- Images across the whole site: 54.4 MB, now 4.2 MB
- Third-party requests: 12, now zero
Nothing clever was involved. The images were converted to WebP at a sensible maximum width, given real dimensions so the layout stops jumping while they load, and told to load lazily below the fold. The interesting part is not the technique, it is that nobody had measured.
Checks
Three checks that would have caught the actual bugs
There is no unit test suite here, because there is no business logic worth unit testing. Testing DOM glue with a fake DOM would not have caught a single real problem on this site.
What exists instead is one command that checks the things that actually broke: every internal link and anchor resolves, every design token is defined, and every page has one heading and images with alt text and dimensions. It found three broken accessibility links the first time it ran. It runs before every deploy, and it needs nothing installed.
Honesty
What was wrong before
It would be easy to present this as though it were designed this way. It was not. A review of my own site found, among other things, that the headline numbers on my homepage were rewriting themselves as visitors scrolled, counting downward until the flagship project advertised 69% growth instead of 230%. Two scripts were fighting over the same elements, and each was reading its target from the very text it was overwriting.
A separate script was replacing the closing pitch on my best case study with a generic summary, and printing it twice. The contact form showed error styling before anyone typed and hid its own send button after a successful message. On a phone, there was no visible way into any case study at all.
All of that was live, and some of it had been live for months. I am including it here because a colophon that only lists good decisions is marketing. The useful part of this work was measuring honestly, reproducing each failure before believing it, and then proving it fixed.
Stack
What is actually here
- Hand-written semantic HTML, fifteen pages
- CSS with custom properties as design tokens, one file per component
- ES modules, no bundler, no transpiler, no polyfills
- A 150 line zero-dependency build, and a zero-dependency validator
- Self-hosted variable fonts and an inline SVG icon sprite
- Netlify hosting, with the enforced CSP, HSTS and Permissions-Policy set in config
No analytics, no cookies, no consent banner, and no tracking of any kind. That is a deliberate choice too, and it is why the privacy policy is short.
Want to see the work rather than the wiring?
The case studies are where the design and research live. This page is just the part underneath them.