Search engines care about page speed. Users care about page speed even more — and unlike a decade ago, Google now measures it precisely through Core Web Vitals: LCP (Largest Contentful Paint, target ≤ 2.5s), INP (Interaction to Next Paint, target ≤ 200ms, which replaced FID in March 2024), and CLS (Cumulative Layout Shift, target ≤ 0.1). A slow site doesn’t just frustrate visitors; it drops in rankings and loses conversions.
Here’s what actually moves the needle today.
1. Stop bundling everything into one giant file
Old advice said to concatenate all CSS and JS files into a single bundle to reduce HTTP requests. That made sense when browsers could only open 2–6 parallel connections per server over HTTP/1.1.
That’s no longer true. HTTP/2 and HTTP/3 multiplex many requests over a single connection, so dozens of small files are often faster than one massive bundle — because the browser can fetch only what each page actually needs and cache the rest granularly. One unchanged byte in your mega-bundle and the user re-downloads everything.
What to do instead:
- Split code by route or feature (most modern bundlers — Vite, esbuild, Rollup, Webpack — do this automatically).
- Lazy-load JavaScript that isn’t needed for the first paint with dynamic
import(). - Make sure your server actually serves over HTTP/2 or HTTP/3. Check with your hosting provider, your browser’s DevTools (Network tab → “Protocol” column), or a tool like KeyCDN’s HTTP/2 Test.
2. Use a CDN — but for the right reasons
CDNs still matter, but the old “shared jQuery from Google’s CDN will already be in the user’s cache” Argument is dead. Browsers now use cache partitioning (per top-level site) for privacy reasons, so cross-site cache hits no longer happen. Don’t pull libraries from public CDNs hoping for a free cache hit — you’re just adding a DNS lookup and a connection to another origin.
The reasons CDNs still help:
- Geographic proximity — edge servers near your users cut latency dramatically.
- HTTP/3 and modern TLS — most CDNs handle this for you.
- Edge caching of HTML and assets — your origin barely gets touched.
- Image optimisation on the fly — many CDNs auto-convert to WebP or AVIF and resize for the requesting device.
Cloudflare, Fastly, Bunny, and Vercel/Netlify edge networks are all solid choices. Self-host your libraries through your CDN rather than pointing at someone else’s.
3. Optimise your images — this is usually the biggest win
Images typically account for 50–70% of a page’s weight, and most sites get this wrong.
- Use modern formats. AVIF is roughly 20–30% smaller than WebP, and WebP is 25–35% smaller than JPEG. Serve AVIF with WebP and JPEG fallbacks via
<picture>. WebP support sits at around 95% of browsers, and AVIF at around 94%, so a fallback chain still matters. - Always set
widthandheightattributes so the browser reserves space and doesn’t shift the layout (this directly improves your CLS score). - Use
loading="lazy"on images below the fold — it’s a built-in browser feature now, no library needed. - Use
srcsetSo phones don’t download desktop-sized images. - Preload the LCP image
<link rel="preload" as="image">so it starts downloading immediately.
<img
src="hero.avif"
srcset="hero-480.avif 480w, hero-960.avif 960w, hero-1920.avif 1920w"
sizes="(max-width: 600px) 480px, 100vw"
width="1920" height="1080"
alt="Hero image"
fetchpriority="high"
>
4. Reduce JavaScript — full stop
The single biggest performance regression of the last decade is the amount of JavaScript shipped to browsers. JS is expensive twice over: download cost and parse/execute cost on the user’s CPU, which hurts mid-range Android phones more than fancy laptops.
- Audit your bundle with tools like
source-map-exploreryour bundler’s analyser. Delete what you don’t use. - Consider whether you really need a SPA framework for a content site. Astro, Eleventy, and plain server-rendered HTML are often the right answer.
- If you do use React/Vue/Svelte, prefer their server-rendering or static-generation modes (Next.js, Nuxt, SvelteKit) so the page works before JS arrives.
- Defer non-critical scripts with
deferorasync. Third-party scripts (analytics, chat widgets, ad tags) are usually the worst offenders — load them late or don’t load them at all.
5. Set proper cache headers
The “Expires” header advice from 2013 still holds, just with newer syntax. Use Cache-Control with long max-ages for fingerprinted assets:
Cache-Control: public, max-age=31536000, immutable
Pair this with hashed filenames (app.a3f9c2.js) so a new deploy gets a new URL and the old version stays cached harmlessly. For HTML, use a short max-age or no-cache So users see new content quickly.
6. Cache on the server (and at the edge)
Server-side caching is still valuable, and it’s easier than ever:
- Edge caching through Cloudflare Workers, Vercel, or Netlify can serve cached HTML in milliseconds from a location near your user.
- Incremental Static Regeneration (Next.js, Nuxt) gives you static-page speed with dynamic-page freshness.
- Object caches like Redis or Memcached are still the right tool for expensive database queries or rendered fragments.
Don’t forget to invalidate when content actually changes — a stale cache is the second-hardest problem in computer science for a reason.
7. Avoid render-blocking resources
A <link rel="stylesheet"> in the <head> blocks rendering until that CSS arrives. A <script> without defer or async blocks parsing. Both murder your LCP.
- Inline the critical CSS for above-the-fold content directly in the HTML.
- Load the remaining CSS asynchronously.
- Add
deferto scripts that don’t need to run before page load. - Use
<link rel="preconnect">orrel="dns-prefetch">For third-party origins, you’ll definitely hit.
8. Watch out for layout shifts and slow interactions
Two of the three Core Web Vitals aren’t about download speed at all:
- CLS (Cumulative Layout Shift, target ≤ 0.1) — caused by images without dimensions, web fonts swapping, and ads loading in. Reserve space for everything.
- INP (Interaction to Next Paint, target ≤ 200ms) — measures how quickly your page responds to clicks and taps across the entire visit (not just the first interaction, like the older FID metric). Long JavaScript tasks block the main thread. Break them up, or move heavy work to a Web Worker. INP is the metric most sites currently fail.
9. Measure, don’t guess
Stop optimising unquestioningly. Use:
- Chrome DevTools Lighthouse — built into the browser, gives you a full Core Web Vitals breakdown.
- PageSpeed Insights — Google’s hosted version includes real-user data from Chrome users.
- WebPageTest — for deep filmstrip analysis and testing from different locations and devices.
- Real User Monitoring (RUM) tools like Cloudflare Web Analytics, Vercel Speed Insights, or Sentry Performance, to see what your actual users experience.
The numbers from real users on real phones are what Google ranks you on — and what your customers actually feel.
The fundamentals haven’t changed: send less, send it closer, send it once. But the specifics of how you do that have shifted enormously since the bundling-and-sprites era. Modern browsers, HTTP/2+, modern image formats, and Core Web Vitals all give you better tools and clearer targets than developers had a decade ago.
Comments