01 — Frontend Performance: The High-Priority Path (Reduce, Ship, Don't Block)
The 19 items roadmap.sh marks High Priority on its Frontend Performance Best Practices checklist look like a wall of one-line rules. The model that organizes them for me is just three phases, in the order a browser actually experiences the page:
- Reduce what you ship — the bytes have to cross the wire. Fewer of them is the cheapest win.
- Ship them faster — compress, cache, sit close, avoid round trips that waste the budget.
- Don't block render — once the bytes start arriving, get out of the browser's way so it can paint.
Six numbers underpin the whole list. Two are the targets, four are the inputs that move them:
I keep that diagram in my head whenever I open DevTools. It's also why the list isn't 19 unrelated rules — every item is one move on one of those three lanes, and two numbers on the right tell me if I'm winning.
The two targets the whole list defends
Keep page weight < 1500 KB (ideally < 500 KB), and keep page load time < 3 seconds. These two are the score, not the levers [1]. Everything else on the High Priority list is a way to move them. The 1500 KB figure is the cliff — past it, mobile data plans, slow CPUs, and congested networks all punish you disproportionately. The 500 KB stretch goal is closer to where pages _feel_ instant. I check both with the Lighthouse audit and the Network panel's "Transfer Size" column (the compressed bytes, not the decompressed size) — and I keep an eye on _what kind_ of bytes: a 500 KB page that's mostly one giant hero JPEG hurts differently than a 500 KB page that's 200 KB of JS framework.
Reduce what you ship
The first lane is pure subtraction. None of it requires a build step or a CDN contract.
- Compress images / keep the image count low — images are almost always the biggest line on the transfer report. Two levers: pick the right format (see below) and ask honestly if every image earns its bytes. A hero needs to be sharp; the seventh avatar in a testimonial carousel doesn't.
- Choose your image format appropriately — WebP/AVIF for photos where the browser supports them (which is most browsers now), SVG for anything that's actually a shape, PNG only when you need alpha _and_ lossless, JPEG when you need a tiny fallback. The format decision is the single highest-leverage byte decision on a typical page.
- Minify your JavaScript and minify your CSS (remove comments, whitespace) — production builds strip white space and comments, rename short identifiers in JS, and collapse what they can in CSS. With a modern bundler this is a default, not a knob; the failure mode is shipping a debug build or an un-minified vendor script. Verify in the response: minified code reads as one long line, pretty-printed code has indentation.
- Minimize HTTP Requests — the older rule from the HTTP/1.1 era, where each request was a serial round trip. With HTTP/2 multiplexing (many requests sharing one connection), it's less about request _count_ and more about request _cost_: every request still pays DNS, TLS, and a TTFB. The lesson survives as "don't request what you won't use" — dead tracking pixels, an unused CSS framework, an icon font that ships 400 glyphs to render six.
The throughline: bytes that don't ship arrive instantly. Negotiate every byte before adding it.
Ship it faster
Once bytes must ship, the question is latency and repetition. Six High Priority items live here.
- GZIP / Brotli compression is enabled — text responses (HTML, CSS, JS, JSON, SVG) should arrive compressed. Brotli is supported by all modern browsers and is ~15-20% smaller than gzip on text. This is a server/CDN setting, not a build change, and it cuts the largest text-asset bytes for free. Verify with Content-Encoding: br (or gzip) in the response headers.
- Set HTTP cache headers properly — Cache-Control is the lever that turns a one-time download into a repeat visit that costs nothing. Long max-age for hashed assets, must-revalidate with ETag for HTML, no-store for anything user-specific. A misconfigured cache silently re-downloads everything on every navigation.
- Keep the Time To First Byte < 1.3 seconds — TTFB is the time from request to first byte of the response, and it's the floor on everything else: no compression or minification helps if the server takes 2 s to start talking. 1.3 s is loose; I aim for under 0.6 s on a warm cache. Causes, in order of how often I see them: slow origin, cold serverless cold-starts, no CDN in front of dynamic content, blocked render-blocking on the server itself.
- Use HTTPS on your website — performance, not just security: HTTP/2 and HTTP/3 (which genuinely affect speed) require TLS. HSTS so the redirect is free. There's no modern performance story on plain HTTP.
- Avoid requesting unreachable files (404) — a 404 still pays DNS, TLS, and TTFB, and unlike a 200 it pays them for nothing. Common sources are moved assets, dead icon paths, and a favicon that was never uploaded. The Network panel filtered to status >= 400 finds them in seconds.
- Serve files from the same protocol — don't pull http:// assets into an https:// page. Mixed-content blocking prevents some of these from loading at all, and the ones that do load forfeit the connection reuse that same-origin HTTPS would give them. The fix is almost always a protocol-relative URL (//host/path) or just https:// everywhere.
This lane is mostly configuration, and mostly invisible when it works — which is why it's worth auditing explicitly a couple of times a year.
Don't block render
The third lane is about the work the browser does _after_ bytes arrive. Render-blocking resources are anything that says "wait for me before you paint": synchronous <script> tags, <link rel="stylesheet"> in the head, large inlined CSS, iframes.
- Non-Blocking JavaScript: use async / defer — the rule that pays off the most. A plain <script src> in the head pauses HTML parsing, downloads, executes, then resumes. defer keeps parsing, downloads in parallel, and runs after the DOM is ready in order — right for app scripts. async downloads in parallel and runs the moment it's ready, out of order — right for independent widgets (analytics, ads). Default to defer; reserve async for things that genuinely don't depend on the page.
- Inline the Critical CSS (above the fold) — extract the styles needed to paint the first viewport and inline them in <head>; lazy-load the rest. The trade is bytes-in-HTML (which can't be cached separately) for a faster first paint; only the styles above the fold belong inlined, everything else stays external where it can be cached.
- CSS files are non-blocking — modern browsers don't block rendering on later-arriving stylesheets if the inline critical CSS already lets them paint. Don't fight this: keep your main stylesheet external with a proper Cache-Control and let the inlined critical CSS hold the first paint.
- Avoid the embedded / inline CSS — separate from "inline the critical CSS." This rule targets style="…" attributes and <style> blocks scattered through the body: they bypass the cache, can't be revalidated, and bloat the HTML on every navigation. Inline the small critical stylesheet once in <head>; everything else goes in an external file.
- Minimize number of iframes — each iframe is a separate document with its own request, its own CSS, its own JS. They block load until they finish, they cost memory, and they're commonly the slowest thing on an otherwise fast page. Embed third-party widgets only when there's no lighter alternative, and lazy-load them when you must.
- Analyse stylesheets complexity — selectors that match huge swaths of the DOM (*, body *, deep descendant chains) make style recalculation expensive on every interaction. Chrome DevTools' Coverage tab and the Performance panel's "Recalculate Style" task show the cost. The fix is usually narrower selectors and fewer of them, not more !important.
The shared idea is _don't make the browser wait on something it doesn't need waiting on_. The browser is concurrent; your job is to leave it that way.
How I run the list
When I audit a page I work the lanes in the diagram's order: reduce first (cheapest, no infra), then ship faster (mostly headers and a CDN), then unblock render (script tags and CSS layout). I check the two targets in the right panel between every change. The single highest-leverage move is almost always image format + compression, because images dominate transfer size and the fix is a build pipeline change, not a refactor. Second is defer and async/defer + critical CSS, because it's where first-paint lives. After that the work is mostly tuning — cache headers, 404 hunt, HTTPS, the occasional iframe removal.
Nineteen items, three lanes, two numbers. The whole High Priority tier collapses to that. Once these are handled, the Medium and Low tiers are where the long tail lives — but getting any of the High Priority items badly wrong will tank the page regardless of how clean the rest is.
References
- [1] roadmap.sh, "Frontend Performance Best Practices," roadmap.sh, 2024. [Online]. Available: https://roadmap.sh/frontend-performance-best-practices
- [2] MDN, "Content-Encoding," Mozilla, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
- [3] web.dev, "Reduce JavaScript execution time," Google, 2024. [Online]. Available: https://web.dev/articles/optimizing-content-efficiency-eliminate-javascript
- [4] MDN, "Defer loading JavaScript," Mozilla, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-defer
- [5] web.dev, "Inline critical CSS," Google, 2024. [Online]. Available: https://web.dev/articles/extract-critical-css
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!