Page Speed Optimisation: A Developer's Checklist for Sub-2-Second Load Times
A 1-second delay in page load time reduces conversions by 7% and increases bounce rate significantly. Google uses page speed as a ranking factor through Core Web Vitals. Optimising for speed is simultaneously an SEO, UX, and revenue decision.
Establish a Baseline First
Before touching anything, measure:
- PageSpeed Insights — lab + field data, actionable diagnostics
- WebPageTest — detailed waterfall, time to first byte, filmstrip
- Chrome DevTools → Network tab — see exactly what loads and when
Record your current scores. Every optimisation should be validated against these baselines.
Target metrics:
- TTFB (Time to First Byte): < 200 ms
- LCP (Largest Contentful Paint): < 2.5 s
- TBT (Total Blocking Time): < 200 ms
- CLS (Cumulative Layout Shift): < 0.1
Layer 1: Server and Network
This is the highest-leverage layer. Server improvements help every single page.
Hosting Quality
Shared hosting TTFB is typically 400–800 ms. VPS or managed hosting: 50–150 ms. Cloud edge (Cloudflare Workers, Vercel Edge): 20–50 ms.
If your TTFB is over 400 ms, switch hosts before optimising anything else.
Content Delivery Network (CDN)
A CDN caches your pages at edge nodes around the world, delivering them from a server close to each visitor.
Cloudflare (free tier available) is the easiest to set up:
- Add your domain to Cloudflare
- Update your nameservers
- Enable "Cache Everything" for static pages
For dynamic sites, use edge caching with cache-control headers:
Cache-Control: public, max-age=3600, s-maxage=86400, stale-while-revalidate=604800
HTTP/2 and HTTP/3
HTTP/2 allows multiplexed requests (multiple files over one connection). HTTP/3 uses QUIC for faster connection establishment. Both are enabled automatically on Cloudflare and modern hosts.
Verify: Chrome DevTools → Network → Protocol column should show h2 or h3.
Layer 2: Images
Images are typically the largest assets on any page. They're also the most optimisable.
Format
| Format | Use For | Savings vs JPEG |
|---|---|---|
| WebP | Photos, complex graphics | 25–35% |
| AVIF | Photos where maximum compression matters | 40–50% |
| SVG | Icons, logos, illustrations | N/A (vector) |
Convert all JPEGs and PNGs to WebP as a minimum. Use AVIF for hero images where browser support allows.
Compression
- Lossy compression at 75–85% quality is visually lossless for most images
- Tools: Squoosh, Sharp (Node.js), ImageMagick, Cloudinary
Responsive Images
<img
src="hero-800.webp"
srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 800px"
alt="Hero image"
width="800"
height="450"
loading="lazy"
/>
In Next.js, <Image> handles all of this automatically.
Priority Loading
The LCP image should never be lazy-loaded. Add fetchpriority="high" or Next.js priority prop:
<Image src="/hero.webp" alt="Hero" priority width={1200} height={630} />
Layer 3: CSS and Fonts
Remove Unused CSS
Unused CSS is loaded, parsed, and held in memory on every page. Tools to find it:
- Chrome DevTools → Coverage tab → reload page → see unused bytes
- PurgeCSS (build-time tool) — removes unused Tailwind/utility classes
- Tailwind CSS — automatically purges unused classes in production
Critical CSS
Inline the CSS needed for above-the-fold content directly in <head>. Defer everything else:
<style>/* critical CSS here */</style>
<link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
Web Fonts
Fonts block rendering if not loaded efficiently:
<!-- Preconnect to font provider -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<!-- Preload the specific font file -->
<link rel="preload" href="/fonts/inter-v13-latin-regular.woff2" as="font" type="font/woff2" crossorigin />
Use font-display: swap or optional:
swap: text shows in fallback font immediately, swaps when font loads (can cause CLS)optional: uses system font if web font isn't cached (no CLS, slight inconsistency)
Self-hosting fonts eliminates the DNS lookup to external font providers.
Layer 4: JavaScript
JavaScript is the most expensive resource on the web — it must be downloaded, parsed, compiled, and executed before it affects the page.
Defer Non-Critical Scripts
<!-- Blocks rendering: bad -->
<script src="analytics.js"></script>
<!-- Runs after HTML parsed: good -->
<script src="analytics.js" defer></script>
<!-- Runs as soon as downloaded, doesn't block: good for independent scripts -->
<script src="widget.js" async></script>
Code Splitting
In Next.js App Router, code splitting is automatic per route. To split further:
import dynamic from "next/dynamic";
const HeavyComponent = dynamic(() => import("./HeavyComponent"), {
loading: () => <Skeleton />,
});
Audit Third-Party Scripts
Each third-party script (analytics, chat, ads, heatmaps) adds 50–300 ms of main-thread work. Audit quarterly:
- Chrome DevTools → Performance → record page load
- Identify third-party domains in the Network waterfall
- Remove any scripts whose value doesn't justify the cost
Tree Shaking
Only import what you use:
// Bad: imports entire library
import _ from "lodash";
// Good: imports one function (~1/50th the size)
import debounce from "lodash/debounce";
Layer 5: Caching
Browser Caching
Set long cache lifetimes for static assets via Cache-Control headers:
# Static assets (images, fonts, JS, CSS with content hash in filename)
Cache-Control: public, max-age=31536000, immutable
# HTML pages
Cache-Control: public, max-age=0, must-revalidate
Content-hashed filenames (e.g., main.a3f5c2.js) allow infinite caching — the hash changes when content changes, forcing a fresh download.
Service Worker / PWA Caching
For returning visitors, a service worker can serve cached assets instantly, making repeat visits feel instant.
Next.js supports this via next-pwa or custom service workers.
Page Speed Optimisation Checklist
Server & Network
- TTFB under 200 ms
- CDN enabled
- HTTP/2 or HTTP/3 active
- Gzip / Brotli compression enabled
Images
- All images in WebP or AVIF
- Images compressed to < 200 KB each
- Responsive
srcseton all content images - LCP image not lazy-loaded; has
fetchpriority="high" - Width and height attributes set on all
<img>elements
CSS & Fonts
- Unused CSS removed
- Fonts preloaded and self-hosted
-
font-display: swaporoptionalset
JavaScript
- Non-critical scripts deferred
- Third-party scripts audited and minimised
- Dynamic imports used for heavy components
- No render-blocking scripts in
<head>
Caching
- Long-lived Cache-Control headers on static assets
- Service worker caching for repeat visitors