Web Develop Mentor

A field guide for practical engineering by Stella Sage.

Four Things Making Your Page Slow (In the Order I Find Them)

//

//

A fast road at sunset

Last time we fixed the queries. This time: the browser. If your backend is fast and your page still feels slow, the problem is almost never “the internet.” It’s one of four things, in order of how often I find them:

1. Render-blocking assets you forgot existed

Every <script> without defer and every stylesheet in the head blocks first paint. The classic offender is a font request from a third-party domain, sitting at the top of the critical path:

<!-- bad: blocks rendering until the font CDN answers -->
<link rel="stylesheet" href="https://fonts.example.com/...">

<!-- better: preload the actual file, keep it in the critical path on purpose -->
<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>
<style>font-display: swap;</style>

Self-host the font. One request, cached, no third-party latency, no privacy leak.

2. Layout shift from images with no dimensions

Cumulative Layout Shift is the “the button moved and I clicked the wrong thing” tax. The fix is one line most sites never do:

<img src="/photo.jpg" width="800" height="500"
     loading="lazy"
     style="aspect-ratio: 8/5" alt="...">

Set width/height on every image tag (or an aspect-ratio in CSS). Browsers reserve the box before the bytes arrive. In my experience this single habit eliminates most CLS complaints from users.

3. The JavaScript you never audited

Rule of thumb I actually use: if a script isn’t required for the page’s first meaningful interaction, it does not belong in the initial bundle. Analytics, chat widgets, and tag managers ship with enough JS to run a small company. Load them after interaction, or on requestidlecallback:

const start = () => {
  const s = document.createElement('script');
  s.src = '/chat-widget.js';
  s.async = true;
  document.body.appendChild(s);
};
if ('requestIdleCallback' in window) {
  requestIdleCallback(start, { timeout: 3000 });
} else {
  setTimeout(start, 1200);
}

4. Your cache headers are lying to the browser

A PNG with no Cache-Control gets re-validated or re-fetched on every visit. Static assets should be immutable:

Cache-Control: public, max-age=31536000, immutable
Content-Type: image/png

Version your filenames when you change them (app.3f2a.js) and the “immutable” part becomes safe. Do this and the second visit to your site gets dramatically faster — with zero code changes.

The fifteen-minute audit

  1. Open DevTools → Network → disable cache → load your homepage on “Slow 3G”.
  2. Note the first paint. Now enable cache and reload — the delta is your cache-header story.
  3. Sort by transfer size. Anything above 100 KB that isn’t your app is a candidate for lazy-loading or removal.
  4. Check every <img> for missing dimensions. Fix them today.

That’s the whole series so far: the database, the browser, and the two habits that keep both honest. I’ll pick up from here with deployment — where “it works locally” goes to die.

— Stella