This blog exists to be the fastest possible static page on the internet. Design, content, and aesthetics serve performance - never the other way around. The north star: cold-load an article in a single network round-trip after the TLS handshake, anywhere on Earth.
This charter is the contract. Every change in this repo is evaluated against it. If a change violates a budget or principle here, it does not ship.
Linux's default TCP initial congestion window is 10 segments (RFC 6928). With an Ethernet MSS of 1460 bytes, the first TCP flight after the handshake carries ~14,600 bytes of payload. Anything larger pays at least one additional round-trip.
The critical path of every page on this site - HTML, inline critical CSS, and any inlined SVG - must fit under 14 KB, Brotli-q11 compressed. External CSS, images, and any JavaScript are deferred and cannot block first paint. No web fonts, ever.
This is measured by scripts/bench.sh on every build. A build that violates the 14 KB rule is a failed build.
Enforced by scripts/budget.sh:
| Artifact | Budget | Why |
|---|---|---|
index.html minified, Brotli-q11 |
<= 6,000 B | Leaves room for inline CSS in first flight |
| Any post HTML minified, Brotli-q11 | <= 10,000 B | Worst-case long posts |
| Inline critical CSS, Brotli-q11 | <= 8,000 B | Render-unblock the above-fold |
| HTML + inline critical CSS (br11) | <= 14,000 B hard fail | The 14 KB rule |
| Total transferred first paint | <= 20 KB | HTML + critical CSS only |
| JavaScript shipped on first paint | 0 B | No exceptions |
| Deferred CSS (rest of site styles) | <= 25 KB Brotli-q11 | |
| Web fonts shipped, ever | 0 B | System stack only; no @font-face anywhere |
| Total page weight | <= 150 KB | All assets, all requests, cumulative |
| Number of requests on first paint | <= 2 | HTML + (optional) one hashed sprite |
Enforced by scripts/lighthouse.sh via lhci. Each audited page must meet:
- Performance score: 100
- First Contentful Paint: <= 600 ms
- Largest Contentful Paint: <= 800 ms
- Speed Index: <= 800 ms
- Total Blocking Time: <= 50 ms
- Cumulative Layout Shift: <= 0.01
- Time To Interactive: <= 800 ms
- Server response time (TTFB): <= 200 ms
If a change drops any score, either the change is reverted or the charter is amended before merging - never after.
- Hugo extended, exact version pinned in
config/_default/module.toml(module.hugoVersion) and mirrored byHUGO_VERSIONin.github/workflows/perf.ymland the Cloudflare Pages build env var. The asset pipeline is Hugo-native:toCSS(Sass, needs extended),minify,fingerprint- no Node tools at build time. - Theme:
hugo-theme-pagerconsumed as a Hugo Module, pinned ingo.modand vendored under_vendor/so CI and Cloudflare Pages don't need a Go toolchain. CSS and layout live inside the theme - there is no site-level override. Paths below are relative to the theme's root. - Hugo flags on every build:
hugo --minify --gc. No exceptions. - Brotli-q11 precompression of every static asset in CI, shipped alongside the raw files. Cloudflare Pages serves the precompressed files when the client's
Accept-Encodingheader permits it.
Charter-level rules the theme must uphold:
- Critical CSS inlined in
<head>, compiled from above-fold partials and fingerprinted. - Non-critical CSS deferred behind a
rel="preload" ... onload="this.rel='stylesheet'"swap with a<noscript>fallback. - Hashed filenames on every stylesheet so they can ship
Cache-Control: public, max-age=31536000, immutablevia_headers. - No
@importat runtime. Concatenate at build time. - No unused rules. Critical and deferred entry points import only what their tier needs.
The current implementation of the above lives in the theme's assets/scss/ and layouts/ trees; changing it is a theme PR, not a blog PR.
Zero JS on first paint. No frameworks, no jQuery, no analytics pixel. The site is HTML and CSS.
If an interaction genuinely demands JS (search, theme toggle, copy-code buttons), it is gated behind user intent:
<script type="module" async>only.- Loaded on
pointerenter/pointerdown/input focus, never onDOMContentLoaded. - Budget: <= 10 KB gzipped across all deferred scripts combined.
The theme ships no JS by default. Any feature that would need JS must justify its bytes against a user benefit, not against "it's nice to have."
System stack only. No @font-face, no <link rel=preload as=font>, no woff2 shipped. System fonts render instantly, never swap, cost zero bytes, and guarantee Lighthouse 100 without per-OS divergence. If a rendering detail depends on a web font, the design is wrong, not the byte budget.
- LCP image (hero / avatar):
fetchpriority="high",decoding="async", dimensions set to prevent CLS, preloaded in<head>. - Everything else:
loading="lazy"anddecoding="async". - Format priority: AVIF -> WebP -> JPEG, emitted via Hugo's image pipeline.
<picture>element with type-sorted<source>. - Responsive srcset at 1x, 2x for every image. Hugo generates them deterministically.
- No decorative images on the home page or post headers. Text is enough.
- SVG for icons, inlined in the HTML when small, sprited when shared across pages.
- Hashed, content-addressable filenames for every asset (
style.<sha256>.css, etc.). Hugo'sresources.Fingerprinthandles this. Cache-Control: public, max-age=31536000, immutableon all hashed assets (1 year, never revalidated).Cache-Control: public, s-maxage=300, stale-while-revalidate=60on HTML, paired with a zone-level Cache Rule that marks HTML paths on the production hostname as eligible for edge caching with "use cache-control header from origin". Without the Cache Rule, Cloudflare's default cache level skips HTML regardless of the directive. With both in place, CF edge caches HTML for 5 minutes and emitsLast-Modified, so browser revalidation viaIf-Modified-Sincereturns304 Not Modified- typically 0 bytes over the wire after the first visit.stale-while-revalidatelets the edge refresh in the background so deploys propagate within one client request after the TTL lapses._headersfile in the repo root configured for Cloudflare Pages.
- DNS: authoritative on Cloudflare. The production record is a CNAME to the Pages hostname, proxied (orange cloud) in production.
- TTL: 60 s during any cutover, 3600 s in steady state.
- HTTPS / SVCB records (RFC 9460): auto-published by Cloudflare. Carries ALPN hints (
h2,h3) and the ECH config. Reduces first-connect round-trips for capable clients. - TLS 1.3 minimum at the edge. 0-RTT resumption enabled (safe for GET-only static content).
- HTTP/3 + QUIC enabled at the edge. Advertised via
Alt-Svc: h3=":443"; ma=86400. - Brotli precompressed assets served with
Content-Encoding: br. Gzip kept as fallback.
Every change goes through the test suite in scripts/. A PR that does not run clean is not merged.
scripts/bench.sh- build the site, compute raw/gzip/brotli sizes, print the single-packet fit report. Fails loudly if the 14 KB rule is violated.scripts/budget.sh- enforce the byte budgets table above. One exit code per budget. CI-friendly.scripts/lighthouse.sh- spin up a local Hugo server, run Lighthouse vialhci, assert the Core Web Vitals thresholds above. Uses.lighthouserc.local.jsonlocally and.lighthouserc.prod.jsonwhen invoked with--prod. The two configs differ only on server-dependent audits (uses-text-compression,uses-long-cache-ttl): strict in prod,warnlocally becausepython3 -m http.serversends neither Brotli nor cache headers.scripts/http-verify.sh- against the deployed URL, verify: TLS 1.3 is negotiated, HTTP/3 is advertised, Brotli is served, immutable cache headers are present on hashed assets, ETag revalidation returns 304 on HTML..pre-commit-config.yaml- local enforcement. Runsscripts/budget.shas aperf-budgethook whenevercontent/,static/,_vendor/,config/, orhugo.tomlchanges. Also standard hygiene hooks (trailing whitespace, EOF fixer, YAML/JSON/TOML linting, shellcheck, 200 KB large-file guard). Install once withpre-commit install. Theme JS lint / typecheck live in the theme repo's own CI, not here.
Tooling required (install once):
brew install nghttp2 brotli hyperfine curl pre-commit go
npm install -g lighthouse @lhci/cli
pre-commit install
Go is needed by Hugo Modules at hugo mod get / hugo mod vendor time; once _vendor/ is populated, builds don't need it. Lighthouse CLI is installed globally. PostCSS/cssnano minification runs through Hugo's built-in pipeline, not a standalone npm install.
The homebrew curl formula ships with HTTP/3 (ngtcp2 + nghttp3) enabled; use /opt/homebrew/opt/curl/bin/curl --http3 for HTTP/3 probes since macOS's system curl does not support it.
- Measurement beats opinion. If a change cannot be measured, it cannot be justified. Before writing code, decide which number in
scripts/bench.shthis change is supposed to move and by how much. - The 14 KB rule is sacred. Every other rule exists to make it easier to hit.
- Subtract before you add. The default answer to "should we add X" is no. The cost of a feature is not the time to implement it; it is the bytes it puts on the critical path for every visitor forever.
- Defer aggressively. JavaScript, non-critical CSS, images below the fold, analytics - all deferred, all optional, all removable without breaking the page. No web fonts in any tier - system stack only.
- Progressive enhancement, not progressive degradation. The page must work with JavaScript disabled and images failing. Everything above that is decoration.
- No third-party requests, ever. Zero. No Google Analytics, no Disqus, no fonts from Google Fonts, no CDN-hosted anything. The browser talks to one origin and that is it.
- Reject frameworks. Hugo is the build tool. The output is HTML + CSS. There is no React, no Vue, no Svelte, no Astro, no runtime framework of any kind. If a feature cannot be expressed in those terms, the feature does not belong here.
- If it is not in
content/, it is noise. Posts are what the reader came for. Navigation, tags, metadata, decoration - all of that is scaffolding that must shrink, not grow.
If a PR violates any rule above:
- The violation is called out by name and budget.
- A concrete plan to bring it back within budget is agreed before the change is made.
- If the plan is not workable, the change is dropped.
"It is only a little over" and "we can optimize later" are not acceptable reasons. Later is now.