Ten pages, no framework, 24.9 KB of text on the home page, and three requests to a domain that isn't mine. Also four things about it I am not happy with, which are at the bottom because they are the interesting part.
It is hand-written HTML. There is no framework, no component layer, no client-side router, and no hydration. Every page is a file that a server can hand over without thinking about it.
The single build step compiles Tailwind's utility classes into a static
stylesheet. Before that, the site loaded the Play CDN from
<head> — about 120 KB gzipped of JavaScript that has
to run and generate the CSS before the first styled paint, on a
build Tailwind documents as development-only. Compiling it ships
4.0 KB gzipped instead of 120, and nothing has to
execute for the page to look right.
The rest is four small JavaScript files totalling 13.1 KB unminified, with no dependencies between them and none from npm: the nav, the cursor, the scroll reveals, and the photography lightbox. The lightbox used to pull GSAP from a CDN for four tweens; it is CSS transitions now.
| Resource | Gzipped |
|---|---|
| index.html | 10.7 KB |
| css/styles.css — hand-written | 5.9 KB |
| css/tailwind.css — compiled | 4.0 KB |
| js/cursor.js | 1.7 KB |
| js/main.js | 1.6 KB |
| js/nav.js | 1.1 KB |
| All markup, styles and script | 24.9 KB |
Measured, not estimated. Served locally and gzipped at default compression. Third-party requests and the hero photograph are excluded here and accounted for below.
197 KB for a first view, and 87% of it is one photograph. Every optimisation described on this page is the small green sliver. That is worth knowing before anyone — me included — spends another evening shaving bytes off a stylesheet.
Values are cheap to write down. These are ones you can verify from the source in about a minute each.
Two greys are defined in css/styles.css and nothing on the site
is allowed to be dimmer: --label at 5.4:1 and
--secondary at 6.4:1 against the background. The accent green
sits at 11:1. This is enforced by a script that parses every stylesheet and
every inline <style> block and fails on anything under
4.5:1 — it caught section labels sitting at 3.5:1 and article dates at
2.7:1, which is below the floor for large text, never mind 11px capitals.
Every scroll animation is gated behind a .js class set in the
document head. If a script fails, the class is never added, nothing is
hidden, and the page is a plain readable document. The mobile nav degrades
to a list of links. The custom cursor only hides the real one
after the replacement is confirmed running — the original set
cursor: none in CSS, which left a broken script with no pointer
at all.
The compiled stylesheet depends on class names in the HTML, so a build that
only ever runs locally drifts the first time someone forgets. GitHub Actions
recompiles it on every push to main and deploys to GitHub
Pages. If the compile produces an empty file, the deploy fails rather than
shipping an unstyled site.
Two go to fonts.googleapis.com and
fonts.gstatic.com for Space Grotesk, the display face. That is
the whole list. There is no analytics vendor on this site — there was a
Google tag, inherited from a template and reporting into a property I could
not find, and it has been removed. The résumé viewer at
/resume/ posts opens to a Google Sheet I own; nothing else
phones anywhere.
All photography is mine, resized through a
sharp script that writes an 800px thumbnail and a 1920px
version and regenerates a manifest. The grid lazy-loads thumbnails; the
full size is only fetched when you open one.
A colophon that only lists good decisions is marketing. These are the open ones, roughly in order of how much they bother me.
fetchpriority="high" — and it
still dwarfs the entire rest of the site. The honest fix is a smaller
crop or no photograph, and I have kept it because I like it. That is a
preference, not an engineering decision, and it should be labelled as
one.
prefers-reduced-motion, and it still replaces a control the
operating system already got right. I kept it because the site should
feel like a person made it. It is the one piece of this page arguing
against the rest of it.
Everything above is checkable against the repository. If a number here is wrong, that is a bug, and I would like to know.