The three things users actually notice
Performance metrics proliferate, but users perceive three things: how long until something appears, whether it moves once it has appeared, and whether it responds when they touch it. Everything else is diagnostic.
- Time to first meaningful paint. Governed by how much must be downloaded and parsed before anything can be drawn.
- Layout stability. Governed by whether you reserved space for things that arrive late.
- Input responsiveness. Governed by how much JavaScript is occupying the main thread when the tap lands.
Render-blocking is the whole first metric
Stylesheets block rendering by design; the browser will not paint until it knows what things look like. This is correct behaviour and the reason a site with ten small stylesheets can feel slower than one with a single larger file, particularly on a high latency connection.
Our framework splits CSS into ten files for maintainability, which is a deliberate trade. On HTTP/2 the cost is small, but it is not zero. Where first paint on slow connections matters more than editorial convenience, concatenating them at deploy time is a five-line pipeline step and changes nothing about how the source is authored.
Note
Measure before optimising this. On a fast connection the difference between one file and ten is invisible; on a rural 4G connection it is a perceptible pause. Which of those describes your audience is a question about your audience, not about CSS.
Load JavaScript so it never blocks
Module scripts are deferred by default: they download in parallel with parsing and execute after the document is ready. This is almost always what you want.
One entry point, deferred by default
<script type="module" src="/js/main.js"></script>
The corollary is that anything rendered by that script appears after first paint. For navigation and footers this is acceptable if you reserve their space; for the main heading of the page it is not, which is why headline content should be in the HTML.
Reserve space for everything
Layout shift is almost entirely self-inflicted. Images without dimensions, script- rendered regions without a reserved height, and late-arriving banners each move content under the reader's finger at the worst possible moment.
Intrinsic sizing prevents the shift
<img src="/assets/images/blog/engineering.svg"
alt=""
width="1200"
height="800"
loading="lazy"
decoding="async">
The width and height attributes do not fix the rendered size
when CSS overrides them; they give the browser an aspect ratio so it can reserve the
right box before the bytes arrive.
Watch out
Do not lazy-load the image at the top of the page. loading="lazy" on the
hero delays the one image that determines the perceived load time. Lazy-load
everything below the fold and nothing above it.
Fonts are the usual culprit
A single webfont family in four weights is commonly the largest render-affecting
payload on a corporate site, and it arrives after the CSS that needs it. The options
are to subset aggressively, to accept a flash of fallback text with
font-display: swap, or to use the fonts already on the device.
This framework takes the third option. A carefully chosen system stack costs nothing, never flashes, and looks native on every platform. It is a real constraint on brand expression and, for most organisations, a good trade.
Fetch less, and fetch it later
Content loaded from JSON should be fetched once and only where it is used. Two habits cover most of it: request the index file a single time per page, and do not request it at all when the page contains no hook for it.
Cache the promise, not the result
const cache = new Map();
export function loadJson(path) {
if (!cache.has(path)) {
cache.set(path, fetch(url(path)).then((r) => (r.ok ? r.json() : null)));
}
return cache.get(path);
}
Caching the promise rather than the resolved value means concurrent callers share one request instead of racing to make three.
A budget, written down
Performance decays silently unless someone has agreed what "too slow" means. We set page-weight budgets per template and check them in the pipeline.
| Template | HTML + CSS + JS | Images |
|---|---|---|
| Homepage | Under 120 KB | Under 400 KB |
| Article | Under 120 KB | Under 250 KB |
| Legal page | Under 100 KB | None |
Figures shown are illustrative budgets for a demonstration site, not measured results.
The principle underneath all of it
Every performance rule reduces to the same instruction: do less work before the user can read something. Fewer bytes, fewer round trips, fewer things that must complete before paint. Framework choice, image format and caching strategy are all just different ways of obeying it.