Your PageSpeed Insights score is not just a number — it’s a direct reflection of real user experience. This guide takes you step by step from the easiest wins to advanced optimizations.
🎯 Understanding Your Score
Score ranges:
──────────────────────────────────────────────
0 ──────── 49 🔴 Poor (hurts rankings)
50 ──────── 89 🟡 Needs improvement
90 ─────── 100 🟢 Good (SEO ideal)
──────────────────────────────────────────────
How the score is calculated:
├── LCP 25% weight
├── TBT 30% weight ← Total Blocking Time
├── CLS 15% weight
├── FCP 10% weight ← First Contentful Paint
└── Speed Index 10% weight
📸 Phase 1 — Images (Biggest Impact)
Images are the lowest-hanging fruit for most websites and usually deliver the largest score gains.
1.1 — Convert All Images to WebP
# Before
hero.jpg → 280 KB ❌
product.png → 340 KB ❌
banner.jpg → 180 KB ❌
# After converting to WebP
hero.webp → 85 KB ✅ (-70%)
product.webp → 120 KB ✅ (-65%)
banner.webp → 60 KB ✅ (-67%)
Expected score impact: +8 to +15 points
1.2 — Preload Your Hero Image
<!-- Place this in <head> as early as possible -->
<head>
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />
</head>
Expected LCP improvement: -0.5 to -1.5 seconds
1.3 — Lazy Load Below-the-Fold Images
<!-- ✅ Images below the fold -->
<img src="section.webp" loading="lazy" width="800" height="400" alt="Section" />
<!-- ✅ First 2–3 images — do NOT use lazy -->
<img src="hero.webp" loading="eager" fetchpriority="high" />
Expected impact: -30% in initial request payload
1.4 — Always Declare Image Dimensions
<!-- ❌ No dimensions — causes CLS -->
<img src="product.webp" alt="Product" />
<!-- ✅ With dimensions — prevents CLS -->
<img src="product.webp" alt="Product" width="400" height="400" />
⚡ Phase 2 — JavaScript (Second Largest Impact)
2.1 — Eliminate Render-Blocking Scripts
<!-- ❌ Blocks page rendering -->
<script src="analytics.js"></script>
<!-- ✅ Non-blocking — executes after parse -->
<script src="analytics.js" defer></script>
<!-- ✅ Non-blocking — executes as soon as available -->
<script src="analytics.js" async></script>
When to use which:
defer → Scripts that depend on the DOM (analytics, widgets)
async → Fully independent scripts (ads, chat widgets)
2.2 — Code Splitting
// ❌ Load everything upfront
import HeavyChart from './HeavyChart';
import LargeTable from './LargeTable';
// ✅ Load on demand only
const HeavyChart = React.lazy(() => import('./HeavyChart'));
const LargeTable = React.lazy(() => import('./LargeTable'));
2.3 — Remove Unused JavaScript
Chrome DevTools → Coverage tab → identify unused code
Common offenders:
lodash.min.js → 71 KB loaded, 8 KB used (89% waste!)
jquery.min.js → 87 KB loaded, 12 KB used (86% waste!)
Fix: replace with smaller alternatives or write native JS
2.4 — Break Up Long Tasks
// ❌ Blocks the main thread for 500ms+
function processLargeDataset(data) {
data.forEach(item => heavyOperation(item));
}
// ✅ Yield to the main thread between chunks
async function processLargeDataset(data) {
for (let i = 0; i < data.length; i++) {
heavyOperation(data[i]);
if (i % 50 === 0) {
await new Promise(r => setTimeout(r, 0)); // yield
}
}
}
🎨 Phase 3 — CSS
3.1 — Remove Unused CSS
# PurgeCSS — automatically removes unused CSS
npx purgecss --css styles.css --content index.html --output purged/
# Real-world example:
bootstrap.css → 197 KB → after Purge → 12 KB ✅ (-94%)
3.2 — Critical CSS (Inline Above-the-Fold Styles)
<!-- ✅ Critical CSS inlined — renders immediately -->
<style>
/* Only CSS needed for above-the-fold content */
body { margin: 0; font-family: ... }
.hero { ... }
.nav { ... }
</style>
<!-- Rest of CSS loads non-blocking -->
<link
rel="preload"
href="styles.css"
as="style"
onload="this.onload=null;this.rel='stylesheet'"
/>
<noscript><link rel="stylesheet" href="styles.css" /></noscript>
3.3 — Minify CSS
styles.css → 45 KB
styles.min.css → 18 KB ✅ (-60%)
Tools: cssnano, PostCSS, LightningCSS
🌐 Phase 4 — Server & Network
4.1 — Enable Brotli or GZIP Compression
Brotli outperforms GZIP by 15–20%:
Uncompressed: styles.css → 45 KB
With GZIP: styles.css → 14 KB (-69%)
With Brotli: styles.css → 11 KB (-76%) ✅
Nginx configuration:
# GZIP
gzip on;
gzip_types text/css application/javascript image/svg+xml;
gzip_min_length 1000;
# Brotli (preferred)
brotli on;
brotli_types text/css application/javascript;
4.2 — Upgrade to HTTP/2 or HTTP/3
HTTP/1.1 → one request at a time (slow)
HTTP/2 → multiplexed requests simultaneously ✅
HTTP/3 → faster + QUIC protocol (no head-of-line blocking) ✅✅
Most CDNs support HTTP/3 automatically
4.3 — Cache Headers
Cache-Control: public, max-age=31536000, immutable
Optimized images → max-age=1 year (never change after deploy)
HTML pages → max-age=0 (always revalidate)
CSS/JS files → max-age=1 year + content hash versioning
4.4 — Use a CDN
Without CDN:
User → hits single origin server → geographic latency
With CDN (Cloudflare, AWS CloudFront, etc.):
User → hits nearest edge node → < 30ms latency ✅
Cloudflare's free plan works well for most sites.
🔤 Phase 5 — Web Fonts
<!-- ✅ Preload the primary font -->
<link
rel="preload"
href="/fonts/main.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<!-- ✅ font-display: swap prevents invisible text (FOIT) -->
@font-face{
font-family: 'Main Font';
src: url('/fonts/main.woff2') format('woff2');
font-display: swap;
}
📊 Score Impact by Optimization
| Optimization | Expected Gain | Difficulty |
|---|---|---|
| Convert images to WebP | +8–15 points | Easy ⭐ |
| Preload LCP image | +5–10 points | Easy ⭐ |
| Lazy loading | +3–7 points | Easy ⭐ |
| Remove unused CSS | +5–10 points | Medium ⭐⭐ |
| defer/async for JS | +5–15 points | Medium ⭐⭐ |
| Enable Brotli | +2–5 points | Medium ⭐⭐ |
| Add a CDN | +5–15 points | Medium ⭐⭐ |
| Critical CSS | +5–10 points | Hard ⭐⭐⭐ |
| HTTP/3 | +3–8 points | Hard ⭐⭐⭐ |
🗓️ 3-Week Action Plan — From 60 to 90+
Week 1: Images (biggest impact, easiest to implement)
✅ Convert all images to WebP
✅ Preload the hero image
✅ Add lazy loading to below-the-fold images
✅ Add width/height to every image tag
Expected gain: +15–25 points
Week 2: JavaScript
✅ Add defer/async to all non-critical scripts
✅ Remove unused third-party libraries
✅ Break up long tasks
Expected gain: +8–12 points
Week 3: CSS + Server
✅ Remove unused CSS
✅ Enable Brotli compression
✅ Set correct Cache-Control headers
✅ Set up a CDN
Expected gain: +5–10 points
Total expected: from ~60 → above 90 🟢
✅ PageSpeed Checklist
- [ ] Run PageSpeed Insights and record your baseline score
- [ ] Identify your LCP element
- [ ] Convert all images to WebP (or AVIF for hero)
- [ ] Add
<link rel="preload">for the LCP image - [ ] Add
loading="lazy"to all below-the-fold images - [ ] Add
widthandheightto every<img>tag - [ ] Defer or async all non-critical JavaScript
- [ ] Remove unused CSS and JavaScript
- [ ] Enable Brotli on your server
- [ ] Set up a CDN
- [ ] Re-run PageSpeed Insights and compare
🔗 Related Articles:
- Core Web Vitals ← Understand LCP, CLS, and INP in depth
- WebP Guide ← How to convert images quickly
