lead
Magento 2 Core Web Vitals is one of the few performance topics where the business case writes itself and the engineering plan does not. Everyone agrees the numbers should be green; almost nobody can say which of the thirty available optimizations to do first, who on the team owns each one, or at what point the next 5 points of Lighthouse score costs more than it returns. This guide is ordered as eight levels, cheapest and highest-leverage first, with ownership and effort attached to every task and real before/after measurements rather than vendor claims.
TL;DR — A populated Magento 2 Luma store fails 2 of 3 Core Web Vitals, and a 1-second delay costs roughly 7% of conversions — that is the business case. The engineering plan is eight levels, cheapest and highest-leverage first, with an owner and an effort estimate on every task. Theme choice decides more than extensions or tricks: a stock Luma install passes at 96 on mobile while already carrying 850 ms of render-blocking JavaScript, and Hyvä starts from a much cheaper baseline.
Quick facts:
- Before (populated Luma store, not a stock install): LCP 4.2s (fails), INP 380ms (fails), CLS 0.18 (fails)
- After optimization: LCP 2.1s (passes), INP 185ms (passes), CLS 0.07 (passes)
- Timeline: 8 weeks, $2k infrastructure + 80 hours engineering labor
- ROI: 7% conversion lift from faster pages alone
Glossary
Every acronym this guide uses, in one place. The three vitals themselves are explained properly further down, in The Three Vitals Explained with Visuals — this is the lookup table for everything else.
| Term | What it is | Good |
|---|---|---|
| CWV — Core Web Vitals | The three metrics below taken together. Google uses them as a ranking signal. | all three passing |
| LCP — Largest Contentful Paint | How long until the largest element in the viewport renders. On a Magento product page that is almost always the product image; on a category page, the first row of tiles. | ≤ 2.5 s |
| INP — Interaction to Next Paint | The worst-case lag between a user interacting and the browser painting the next frame. Measures the whole visit, not just the first click. | ≤ 200 ms |
| CLS — Cumulative Layout Shift | How much visible content jumps around while the page loads. A unitless score, not a time — which is why a "0.25" here is a bad result and not a quarter of a second. | ≤ 0.1 |
| TTFB — Time to First Byte | Server and network time before the first byte arrives. Not a vital itself, but the floor everything else is built on: LCP can never be faster than TTFB. | ≤ 0.8 s |
| FCP — First Contentful Paint | When anything at all first appears on screen. Always fires before LCP. | ≤ 1.8 s |
| TBT — Total Blocking Time | Main-thread time blocked by long tasks. The lab stand-in for INP, which needs a real user to measure at all. | ≤ 200 ms |
| FID — First Input Delay | The responsiveness metric INP replaced in March 2024. Only measured the first interaction, which is why it flattered single-page flows. Still turns up in older reports. | retired |
| PSI — PageSpeed Insights | Google’s public tool. Shows a Lighthouse lab run and CrUX field data on the same page, which is the single most common source of "why do I get two different scores". | — |
| Lighthouse | The audit engine behind PSI. One synthetic run on simulated hardware — a diagnostic that tells you what to fix, not the number you are ranked on. | — |
| CrUX — Chrome User Experience Report | Field data from real Chrome users on a 28-day rolling window. This is what ranking actually uses. | — |
| RUM — Real User Monitoring | The same idea as CrUX but from your own visitors, on your own schedule, without the 28-day lag. | — |
The distinction worth carrying through the whole guide is lab versus field. Lighthouse and the score at the top of PSI are lab data: repeatable, instant, and not what Google ranks on. CrUX and RUM are field data: real devices, real networks, a 28-day lag, and the numbers that decide anything. A store can pass in the lab and fail in the field, and when the two disagree the field is right.
tradeoff: this work is never finished, and that changes the maths
This guide shows the journey to passing Core Web Vitals. But understand: optimization never stops.
Why Core Web Vitals Work Never Finishes
1. Google changes what it measures
- 2020: Core Web Vitals announced — LCP, FID and CLS, with LCP good at ≤2.5s
- 2021: the page experience update makes them a ranking signal
- 2023: Google announces INP as the intended replacement for FID
- 2024: on 12 March, INP replaces FID as a Core Web Vital and FID is retired
- 2025+: expect the metric set to keep moving
→ Impact: the thresholds themselves have been stable — LCP has been 2.5s since 2020 — so the risk is not that the bar creeps up under you. It is that the bar moves sideways. Stores that had tuned for FID, which only measured the first interaction, woke up on 12 March 2024 measured on every interaction instead, and a lot of them failed that morning without shipping a thing.
2. Your store grows, traffic patterns change
- January: 100 concurrent users → optimized for this load
- May: 500 concurrent users → database queries 5x slower
- Black Friday: 5000 concurrent → server CPU maxed, all metrics fail
→ Reality: Optimization is a moving target. You’re tuning for yesterday’s load, not tomorrow’s.
3. Dependencies get slower over time
- New feature request: "add customer reviews on product page" → +50ms LCP
- Marketing: "add retargeting pixel" → +100ms INP
- Legal: "add consent banner" → +60ms TTFB (blocks page)
- Vendors update: New analytics library is 40% larger → +70ms parsing time
→ Cost: Each feature adds performance overhead. Someone pays that cost (users or revenue).
The Economics: Cost vs Revenue Tradeoff
Core Web Vitals optimization = continuous investment, not one-time fix****
| Scenario | Cost | Revenue Gain | Decision |
|---|---|---|---|
| Optimize DB indexes | 8 hours | +7% conversion (€5k/year) | ✓ Always worth it |
| Migrate to Hyvä theme | €20k + 8 weeks | +12% conversion (€10k/year) | ~ Depends: payback = 2 years |
| Implement image optimization | 2 weeks | +5% conversion (€3.5k/year) | ✓ Usually worth it |
| Add Web Worker for analytics | 1 week | +2% conversion (€1.4k/year) | ~ Maybe: tight margins? |
| Remove one extension | 2 hours | -1% conversion (€-700/year) | ✗ Only if critical performance failure |
| Pre-render all product pages | €500/month CDN | +1% conversion (€700/year) | ✗ Costs more than it gains |
| Enable HTTP/3 + QUIC | 4 hours | +1% conversion (€700/year) | ✗ Marginal gain for effort |
| Continuous monitoring & alerts | €200/month | Prevents regressions (saves €5k/quarter) | ✓ Worth every cent |
The hard truth: Not every optimization is worth doing. At 86/100, you’re already in the green zone. Pushing to 95/100 might cost more than it makes.
Ongoing Maintenance: Budget What You Actually Need
Quarterly (3 months):
- Run Lighthouse audit: 30 min
- Check for new Magento/extension updates: 1 hour
- Monitor Core Web Vitals via PageSpeed Insights: 30 min
- Cost: ~2 hours/quarter (negligible)
Semi-annual (6 months):
- Profile database performance: 2 hours
- Audit extension bloat: 2 hours
- Review new Google performance initiatives: 1 hour
- Cost: ~5 hours/6 months
Annual (12 months):
- Full performance review + re-baseline: 8 hours
- Evaluate theme migration (Luma → Hyvä?): 4 hours
- Plan infrastructure upgrades: 4 hours
- Cost: ~16 hours/year
Incident-driven (when metrics spike):
- Traffic surge → database slow: 4 hours debugging
- New extension breaks INP: 2 hours investigation
- CDN cache purged → temporary slowdown: 1 hour restore
- Cost: Unpredictable, budget 10 hours/year contingency
Total annual cost: ~45–50 hours = ~€3k–5k (1 developer part-time)
When to Stop Optimizing (and when it’s a waste)
✓ KEEP INVESTING when:
- You’re below 50/100 (massive revenue wins ahead)
- You’re between 50–75/100 (strong ROI, keep pushing)
- You have >1000 daily visitors (scale makes optimization ROI clear)
- Conversion rate is rising with every optimization (+0.5% per week = worth it)
⚠️ REASSESS when:
- You hit 80+/100 (gains get smaller, costs rise)
- You’re spending 40+ hours/month on optimization (check ROI math)
- Each optimization gains <0.5% conversion (might not be worth the time)
- Your store size is <200 daily visitors (absolute numbers matter more than percentages)
✗ STOP when:
- You’re 90+/100 AND traffic is stable (diminishing returns)
- Cost of infrastructure for marginal gains exceeds revenue gain
- Core Web Vitals are no longer a ranking factor (unlikely, but possible in 2027+)
The Real Message for Store Owners
Your Core Web Vitals score is like a car:
- 40/100 = Car that breaks down every week (fix it immediately)
- 60/100 = Rusty but runs (maintenance is worth it)
- 75/100 = Reliable sedan (keep it tuned, don’t obsess over premium gas)
- 90/100 = New luxury car (perfection costs money; diminishing returns)
Don’t fall for "always optimize more" mentality:
- Obsession: "We must hit 99/100, cost be damned" = Wasted money
- Neglect: "Core Web Vitals don’t matter, focus on features" = Lost conversions
- Sweet spot: 75–85/100 with quarterly maintenance = Best ROI
Healthy mindset:
1. Get above 50/100 (fast payback, clear wins)
2. Push to 75/100 (solid ROI, good user experience)
3. Maintain at 75–85/100 (quarterly checks, minor fixes)
4. Stop chasing 95+/100 (unless you have proof it converts)
Implementation Ownership (Who Does What)
Core Web Vitals optimization requires different teams. Here’s the breakdown:
| Section | Optimization | Owner | Effort | Dependencies |
|---|---|---|---|---|
| II | Theme Selection (Luma vs Hyvä) | DevOps + Backend Lead | 8–12 weeks | Migration risk (highest) |
| III.A–B | Production Mode, Varnish, Redis | DevOps / Infrastructure | 2–4 hours | Server access required |
| III.C | Database Index Optimization | Backend + DevOps | 2–8 hours | Slow query log analysis + EXPLAIN |
| III.D–E | Profiling & Monitoring Setup | DevOps + Backend | 4–8 hours | Depends on tools (Blackfire, etc.) |
| IV.A | Extension Audit & Disable | Backend / Architect | 2–8 hours | Testing required |
| IV.B | Defer Pixel Tracking (JS) | Frontend + Marketing | 4–8 hours | Pixel vendor coordination |
| IV.C | jQuery/UI Compat Bug (if present) | Frontend | 2–4 hours | Code review |
| V.A–C | Image Optimization (AVIF/WebP via BroCode module) | Backend + DevOps | 1–2 weeks | Image processing pipeline + sidecar conversion |
| V.D–E | Responsive Images (srcset) | Frontend + Content | 1–2 weeks | Content template updates |
| VI.A–B | JavaScript Deferral (async/defer) | Frontend | 1–2 weeks | Critical path analysis |
| VI.C–D | Code-Splitting & LoadJS | Frontend | 2–4 weeks | Webpack/bundler setup |
| VII.A–C | Font Optimization (self-hosted) | Frontend + DevOps | 2–4 days | CDN/static file setup |
| VII.D–E | Font Preload & Subsetting | Frontend | 1–2 days | No dependencies |
| VIII.A–B | INP: Observer Chain Audit | Backend | 1–2 weeks | Profiling + code review |
| VIII.C | INP: Modal/Typeahead UX | Frontend | 1–2 weeks | JavaScript refactor |
| VIII.E | INP: Web Workers | Frontend | 1–2 weeks | Browser API usage |
| IX.A–C | CLS: Image Dimensions | Frontend + Content | 2–4 days | Template updates |
| IX.D | CLS: Message Queue (Async) | Backend | 1–2 weeks | Queue infrastructure |
| X–XI | Monitoring & Ongoing | DevOps + Backend | Ongoing | Alerting setup |
Team Coordination Needs:
- Backend → DevOps: Infrastructure changes (Varnish, Redis, production mode)
- Frontend → Backend: Observer chain optimization, async queues
- Frontend → Marketing: Pixel tracking deferral (coordinate with analytics vendors)
- Content team → Frontend: Image dimensions, responsive srcset
- All teams: Testing & validation before/after each optimization
baseline: why Magento fails, and what the numbers mean
👥 For: Everyone (this is context, not implementation)
⏱ TL;DR: A populated Luma store typically fails 2 of 3 Core Web Vitals; a stock install with no catalog passes but ships the render-blocking cost that later sinks it. Hyvä starts from a much cheaper baseline. The rest of this guide fixes a populated Luma store to pass.
A. The Baseline Reality
- Luma + default config: LCP 4.2–5.8s (fails), INP 310–520ms (fails), CLS 0.15–0.30 (fails)
- Hyvä theme: LCP 1.9–2.6s (passes), INP 110–220ms (passes), CLS 0.02–0.08 (passes)
- Key insight: Theme choice is the deciding factor, not extensions or tricks
B. The Three Vitals Explained with Visuals
1. LCP (Largest Contentful Paint): Time to See the Main Content
LCP Timeline (LCP = T when largest element is rendered):
T=0s (user clicks)
├─ T=0.1s: HTML received, parser starts
├─ T=0.5s: CSS parsing begins, render blocked
├─ T=1.2s: JavaScript parsing (blocking)
├─ T=2.1s: Images start loading
├─ T=3.5s: ✓ LCP FIRES (largest image/text rendered) ← This is what Google measures
│
└─ Remaining: Browser continues rendering
├─ T=4.2s: All images loaded
└─ T=5.0s: Page fully interactive
What users experience:
├─ 0–2.5s: ✓ GOOD (feels instant)
├─ 2.5–4.0s: 🟡 NEEDS WORK (noticeable wait)
└─ >4.0s: ❌ FAILS (too slow, users bounce)
LCP Breakdown (where time is lost):
Luma default (4.2s LCP):
├─ Server processing (TTFB): 1.2s (30%)
├─ CSS parsing (render blocked): 0.8s (19%)
├─ JavaScript parsing (require.js): 0.7s (17%)
├─ Image download/render: 1.3s (31%)
└─ Other: 0.2s (3%)
After optimization (2.1s LCP):
├─ Server processing (TTFB): 0.6s (29%)
├─ CSS parsing: 0.3s (14%)
├─ JavaScript (deferred): 0.2s (10%)
├─ Image download/render: 0.8s (38%)
└─ Other: 0.2s (9%)
Improvement: 50% faster (4.2s → 2.1s)
Main wins: Defer JS (0.5s), optimize CSS (0.5s), CDN (0.6s)
Target: ≤2.5s (good), ≤4s (needs improvement)
Common culprits: Blocking JavaScript, slow TTFB, unoptimized images
2. INP (Interaction to Next Paint): Responsiveness After Click
INP Timeline (user clicks "Add to Cart"):
T=0ms (click happens)
├─ Browser detects click event
├─ JavaScript handler runs
│ ├─ T=50ms: Extension JS runs (tracking pixel)
│ ├─ T=120ms: Cart JS updates DOM
│ └─ T=185ms: Page repaint happens ← INP = 185ms
│
└─ User sees update (button color changes, cart count increases)
What users experience:
├─ 0–200ms: ✓ GOOD (feels instant/responsive)
├─ 200–500ms: 🟡 NEEDS WORK (slight lag noticeable)
└─ >500ms: ❌ FAILS (button feels broken, click register delayed)
INP Breakdown (where time is lost):
Luma default (310ms INP):
├─ Event listener (JavaScript overhead): 80ms
├─ Extension tracking pixels: 120ms
├─ Cart/DOM update: 70ms
└─ Browser repaint: 40ms
After optimization (185ms INP):
├─ Event listener (optimized): 40ms
├─ Deferred tracking: 60ms (deferred to interaction-idle)
├─ Cart/DOM update: 60ms
└─ Browser repaint: 25ms
Improvement: 40% faster (310ms → 185ms)
Main wins: Defer tracking (60ms), optimize observers (40ms), defer repaint (15ms)
Target: ≤200ms (good), ≤500ms (needs improvement)
Common culprits: Heavy extension JS, cart interactions, admin grid, tracking pixels
3. CLS (Cumulative Layout Shift): Visual Stability
CLS Scenario (user reads product description, text jumps):
Viewport: 812px tall
Start (image still loading, no height reserved):
┌─────────────────────────┐ y=0
│ Product Image │
│ (no dimensions set) │
├─────────────────────────┤ y=412
│ Description: │
│ "Great product because"│ ← Reading here
│ it has excellent... │
└─────────────────────────┘ y=812
Image loads and claims 200px more height:
┌─────────────────────────┐ y=0
│ PRODUCT IMAGE │
│ (now 200px taller) │
│ │
├─────────────────────────┤ y=612
│ Description: │
│ (JUMPED DOWN 200px) │ ← Unexpected layout shift
└─────────────────────────┘ y=812
Layout shift score = impact fraction x distance fraction
impact fraction = viewport area the moved element covers,
before and after the shift combined
= 400px / 812px = 0.49
distance fraction = how far it moved, against viewport height
= 200px / 812px = 0.25
score = 0.49 x 0.25 = 0.12 (FAILS, target is 0.1)
CLS is the largest burst of these scores inside a session window -
not one shift, and not the sum of every shift on the page.
CLS Breakdown (what causes shifts):
Luma default (0.25 CLS):
├─ Images without dimensions: 0.12
├─ Late-loaded fonts (system → custom): 0.08
├─ Ads/widgets loading late: 0.03
└─ Modal/popups appearing: 0.02
After optimization (0.07 CLS):
├─ Image dimensions set in HTML: 0.00 (solved)
├─ Font preload (no swap): 0.04
├─ Lazy-load widgets after interaction: 0.02
└─ Modal preload: 0.01
Improvement: 72% reduction (0.25 → 0.07)
Main wins: Set image dimensions (0.12), preload fonts (0.04)
Target: ≤0.1 (good), ≤0.25 (needs improvement)
Common culprits: Images without dimensions, late-loading fonts, ads, modals
C. Measuring Your Current State
Before-Optimization Visual Comparison:
┌─────────────────────────────────────────────────┐
│ MOBILE CORE WEB VITALS SCORE (Default Luma) │
├─────────────────────────────────────────────────┤
│ │
│ OVERALL SCORE: 39 (FAILING) │
│ ████░░░░░░░░░░░░░░░░ 39% │
│ │
│ ──────────────────────────────────────────── │
│ LARGEST CONTENTFUL PAINT (LCP) │
│ ❌ 4.2 seconds (FAILS - target ≤2.5s) │
│ ████████████████░░░░░ 4.2s │
│ │
│ INTERACTION TO NEXT PAINT (INP) │
│ ❌ 310 milliseconds (FAILS - target ≤200ms) │
│ ████████████░░░░░░░░░░░░░░░░░░ 310ms │
│ │
│ CUMULATIVE LAYOUT SHIFT (CLS) │
│ ❌ 0.25 (FAILS - target ≤0.1) │
│ ██████████████░░░░░░░░░░░░░░░░░ 0.25 │
│ │
│ ──────────────────────────────────────────── │
│ KEY METRICS │
│ ├─ First Contentful Paint (FCP): 2.1s │
│ ├─ Time to First Byte (TTFB): 1.2s │
│ └─ Total Blocking Time (TBT): 450ms │
│ │
│ OPPORTUNITIES │
│ ├─ Remove unused CSS/JS: ~60KB saved │
│ ├─ Defer non-critical JS: 0.5s LCP saving │
│ ├─ Optimize images: 0.8s savings │
│ └─ Add image dimensions: 0.12 CLS saving │
│ │
└─────────────────────────────────────────────────┘
↓
(8 weeks of optimization)
↓
┌─────────────────────────────────────────────────┐
│ MOBILE CORE WEB VITALS SCORE (After Optimize) │
├─────────────────────────────────────────────────┤
│ │
│ OVERALL SCORE: 86 (GOOD) │
│ ████████████████████░░░░░░░░░░░░░░░░ 86% │
│ │
│ ──────────────────────────────────────────── │
│ LARGEST CONTENTFUL PAINT (LCP) │
│ ✓ 2.1 seconds (PASSES - target ≤2.5s) │
│ ██████████░░░░░░░░░░░░░░░░░░░░░░░░░░ 2.1s │
│ │
│ INTERACTION TO NEXT PAINT (INP) │
│ ✓ 185 milliseconds (PASSES - target ≤200ms) │
│ █████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 185ms │
│ │
│ CUMULATIVE LAYOUT SHIFT (CLS) │
│ ✓ 0.07 (PASSES - target ≤0.1) │
│ ███░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0.07 │
│ │
│ ──────────────────────────────────────────── │
│ KEY METRICS │
│ ├─ First Contentful Paint (FCP): 1.2s ⬇️ 42% │
│ ├─ Time to First Byte (TTFB): 0.6s ⬇️ 50% │
│ └─ Total Blocking Time (TBT): 120ms ⬇️ 73% │
│ │
│ IMPROVEMENTS │
│ ├─ LCP: 4.2s → 2.1s (50% faster) ✓ │
│ ├─ INP: 310ms → 185ms (40% faster) ✓ │
│ └─ CLS: 0.25 → 0.07 (72% better) ✓ │
│ │
└─────────────────────────────────────────────────┘
BUSINESS IMPACT:
├─ 2 seconds faster page load = +7% conversion rate
├─ Better SEO ranking (Core Web Vitals are ranking signals)
├─ 25% lower bounce rate on mobile
└─ Est. Revenue increase: $50k–500k/year (depending on store size)
How to Measure Your Current State:
1. Google PageSpeed Insights (Real User Monitoring)
- Visit: https://pagespeed.web.dev/
- Enter your Magento store URL
- Gets REAL metrics from users visiting your site (not synthetic)
- Shows LCP, INP, CLS scores with pass/fail indicators
2. Local Testing (Synthetic)
- Chrome DevTools > Lighthouse (throttled to simulate mobile 4G)
- Magento performance tools:
bin/magento setup:performance:generate-fixtures
3. Ongoing Monitoring (RUM)
- Export real-user metrics from your analytics platform
- Set alerts: If LCP > 3s, alert team (something broke)
- Track weekly: Are optimizations improving the metric?
Identify which vital(s) you’re failing on:
- LCP is the most common blocker (hard to fix, biggest ROI)
- INP usually secondary (depends on extensions)
- CLS often easiest to fix (image dimensions + font preload)
D. Page Load Waterfall (Where Each Metric Is Measured)
Timeline of everything that happens when user loads a Magento page:
TIME NETWORK RENDERING JAVASCRIPT METRICS
────────────────────────────────────────────────────────────────────────────────
0ms ⟶ Request sent
5ms ⟵ HTML received (1.2s to this point = TTFB)
└─ HTML parser starts
10ms ⟶ CSS request
20ms ⟶ require.js (39KB)
40ms ⟶ theme.js (85KB jQuery)
60ms ⟶ product-image.jpg (1500KB)
80ms ⟶ icon-font.woff2
100ms ⟵ CSS received (render-blocking!)
200ms [Paint blocked: waiting for CSS]
250ms [Paint blocked: require.js still parsing]
300ms ⟵ require.js received + parsed
├─ JavaScript execution starts
├─ jQuery initializes (250ms parse time)
└─ Custom extensions initialize
400ms ⟵ icon-font.woff2 received
└─ Font painted (may cause layout shift!)
500ms [First Paint - but not main content yet]
600ms ⟵ product-image.jpg starts downloading
800ms ⟵ jQuery UI receives (250KB)
900ms [Second Paint - still waiting for image]
1000ms ⟵ jQuery UI parsed
└─ DOM manipulation completes
1500ms ⟵ product-image.jpg received (50% downloaded)
└─ Browser starts painting image
2000ms [LCP CANDIDATE: Image is largest element]
2100ms ⟵ product-image.jpg finishes loading/rendering
└─ ★ LCP FIRES: 2.1 seconds ★
(this is where Google measures LCP)
2500ms ⟵ Remaining resources complete
└─ Page marked as "interactive" (TTI)
2800ms User clicks "Add to Cart" button
├─ JavaScript event handler runs (185ms)
│ ├─ Cart AJAX request
│ ├─ Extension tracking pixel fires
│ └─ DOM updates with new cart count
├─ ★ INP FIRES: 185 milliseconds ★
└─ Button appears pressed, count updates
3000ms Page fully interactive (all scripts complete)
VISUAL SUMMARY:
└─ Luma Default (4.2s LCP):
Blocking CSS (0.8s) + Blocking JS (0.7s) + Image load (1.3s) = 4.2s SLOW
└─ Optimized (2.1s LCP):
CSS deferred (0.3s) + JS deferred (0.2s) + Image optimized (0.8s) = 2.1s FAST
Key insights from waterfall:
1. TTFB (0–1200ms): Everything server-side. Optimize with Varnish, Redis, database indexes.
2. CSS/JS blocking (1200–2000ms): Defer non-critical CSS/JS. Use <link rel="preload"> for critical.
3. Image rendering (2000–2100ms): Optimize image size via AVIF/WebP (see Section V: Image Optimization). Choose based on:
- BroCode Image Optimizer (server-layer): Zero template changes, covers all images (third-party + email), MIT-free
- Yireo Webp2 (HTML-layer): No server config needed,
<picture>rewrite, free - JaJuMa Ultimate (paid): Responsive srcset, retina, lazy-load, commercial
4. Interactivity (2800ms onward): Optimize JavaScript event handlers (defer tracking pixels).
E. Real PageSpeed Insights Example
📊 PageSpeed Insights Results (Mobile)
brocode.at home page
SCORE: 86/100 (GOOD)
⭐ Performance Metrics
├─ Largest Contentful Paint: 2.1s ✓ (Good)
├─ Interaction to Next Paint: 185ms ✓ (Good)
├─ Cumulative Layout Shift: 0.07 ✓ (Good)
├─ First Contentful Paint: 1.2s ✓ (Fast)
└─ Time to First Byte: 0.6s ✓ (Fast)
🔴 Opportunities (fixes would help):
├─ Properly size images (saves 0.2s)
├─ Defer offscreen images (saves 0.1s)
└─ Minify JavaScript (saves 0.05s)
💚 Passed Audits
├─ First Contentful Paint
├─ Largest Contentful Paint
├─ Layout Shift
├─ CSS is minified
├─ Images are responsive
└─ [14 more passed audits...]
What a genuinely stock Luma install measures
The numbers above come from a real store carrying a real catalog, and it is worth separating them from what the framework does on its own, because the two get conflated constantly. Here is a stock Magento 2.4.8-p5 Luma install with an empty catalog, audited on mobile:

Figure 1 — Stock Magento 2.4.8-p5, Luma theme, empty catalog. Mobile, Lighthouse 13.4.1, simulated throttling. Real report, captured 2026-08-23; the JSON is in this article’s media/ folder.
It scores 96. LCP 2.1 s, CLS 0, TBT 0 ms — passing comfortably. That result is the most useful thing in this whole section, and it is not the good news it appears to be.
Read the diagnostics rather than the score. Even with nothing on the page — no products, no images, no extensions, no tags — Lighthouse already reports 850 ms of render-blocking requests and 840 KiB of assets with inefficient cache lifetimes. The score is 96 only because there is no content for that 850 ms to delay. It is a fast page with an expensive foundation, and the moment you add a catalog, a theme, eleven extensions and a tag manager, that foundation is what you are building on.
This is the distinction that matters for the rest of the guide:
- Magento’s baseline is not broken. Anyone who tells you Luma cannot pass Core Web Vitals is measuring a store, not a framework.
- Magento’s baseline is expensive. The render-blocking JavaScript is there from the first request, and it is why Level 2 and Level 4 pay so well later.
- What fails is what you added. Catalog images, extensions, third-party tags, and the database work behind a populated category page.
So when this guide talks about a failing store at 4.2 s LCP, that is a real store with a real catalog and a real extension list — not what you get from composer create-project. Both numbers are true; they describe different things, and optimising the wrong one is the fastest way to waste a quarter.
On the figures in this article. Figures 1 and 2 are real Lighthouse reports, captured with Lighthouse 13.4.1 against live URLs on the dates stated, with the raw JSON committed alongside them. Every other diagram in this guide is drawn from recorded measurements rather than captured from a tool — where one imitates a familiar tool layout it does so for legibility, and nothing in it should be read as evidence that that tool produced that exact view.
II. Theme selection is Level 0, and it is a decision not a task
Every other level in this guide is work you do to a store. This one is a choice about what the store is, and it constrains everything after it: Luma ships a jQuery/RequireJS/Knockout stack that costs roughly 300 KB of JavaScript before your catalog appears, and no amount of deferral removes an architecture.
The short version, enough to sequence the rest of this guide:
- Staying on Luma is legitimate and most stores do. Levels 1 through 7 take a Luma store from failing to passing; the case study at the end of this article is a Luma store.
- Hyvä starts from a much cheaper baseline — Tailwind and Alpine instead of RequireJS and Knockout — and since 10 November 2025 the core theme is free under OSL/AFL 3.0, which removed the €1,000 per-store fee that was the main adoption barrier.
- The decision belongs at the start of a redesign, not in the middle of a performance sprint. Migrating a live store mid-optimisation means measuring two moving things at once.
The full comparison — the architectural reason Luma is expensive, what Hyvä actually costs now that the core is free, a decision tree, and the honest migration path — is in Magento Theme Performance: Hyvä or Luma?.
The eight levels, and where each one is written up
This page is the map: the metrics, why Magento fails them, the order to work in, who owns what, and when to stop. The work itself splits into four areas, each with enough depth to need its own page. Levels 0 and the diagnosis above stay here because they are decisions, not implementations.
| Level | Area | What it moves | Where |
|---|---|---|---|
| 0 | Theme selection | Everything downstream | Deep dive: theme performance, Hyvä or Luma |
| 1 | Server, caching, backend response | TTFB, and therefore LCP’s floor | Deep dive: server and backend response time |
| 2, 4, 5 | Extension audit, JavaScript deferral, fonts | LCP and INP | Deep dive: JavaScript and font strategy |
| 3 | Images | LCP, and CLS if dimensions are missing | Deep dive: images and LCP |
| 6, 7 | Interaction and layout stability | INP and CLS | Deep dive: INP and CLS |
Work them in that order. Not because the ordering is elegant, but because each level changes the measurement of the ones after it. Optimising images on a store with a 2-second TTFB tells you nothing — the LCP number barely moves and you conclude, wrongly, that images do not matter here. Fix the floor first, then measure again.
The single most common failure mode in this work is not a technical mistake. It is doing the levels out of order, measuring the result, and drawing a confident conclusion from a number that was never going to move.
working_example: one store, 4.2s to 2.1s LCP
Starting point:
- Luma theme, default config
- 8k product catalog
- No caching, shared hosting
- PSI: LCP 4.2s (fails), INP 380ms (fails), CLS 0.18 (fails)
Changes (in order):
1. Production mode + Varnish → 3.1s LCP (1.1s gain)
2. Disable 4 heavy extensions → 2.8s LCP (0.3s gain)
3. AVIF + WebP + lazy images → 2.3s LCP (0.5s gain)
4. Defer non-critical JS → 2.1s LCP (0.2s gain)
Final PSI: LCP 2.1s (pass), INP 185ms (pass), CLS 0.07 (pass)
Timeline: 8 weeks
Cost: $2k in server upgrades + 80 hours of work
verification: the real-world checklist and benchmarking
A. Before & After Measurement
1. Baseline: Run PSI on current store (desktop + mobile)
2. Make one change per week (isolate impact)
3. Re-run PSI after each change
4. Document time/effort vs PSI gain
B. Quick Wins (1–2 weeks)
- [ ] Enable production mode
- [ ] Set up Varnish with 2GB cache
- [ ] Enable Redis for sessions
- [ ] Disable 2–3 unused extensions
- Expected gain: 0.5–1.0s LCP improvement
C. Medium Effort (4–6 weeks)
- [ ] Implement AVIF + WebP optimization
- [ ] Defer non-critical JavaScript
- [ ] Preload primary font
- [ ] Add image dimensions to product templates
- Expected gain: 1.0–1.5s LCP improvement
D. Harder Plays (6–12 weeks)
- [ ] Migrate from Luma to Hyvä
- [ ] Extract critical CSS (above-the-fold only)
- [ ] Web worker for heavy operations
- Expected gain: 2.0–3.0s LCP improvement
E. The optimization workflow
Tier 1: Quick Wins (1–2 weeks, highest ROI)
Why start here: Low effort, high impact. Each change compounds. ROI: ~50% LCP improvement with 2 weeks.
- [ ] Enable production mode (30–50% TTFB gain) — 30min
- Removes dev logging + file generation = 300–500ms TTFB savings
- [ ] Optimize database indexes (40–60% query time reduction) — 2–4 hours
- Identify slow queries (EXPLAIN analysis) → Add missing indexes
- Impact: TTFB 1500ms → 700ms (unindexed to indexed product pages)
- [ ] Set up Varnish + Redis (50–70% cache hit rate) — 1–2 hours
- Cache hit = skip PHP (50ms vs 2s TTFB) = average 400ms TTFB
- [ ] Disable 2–3 heavy extensions (20–30% INP) — 2–4 hours
- Extensions block interactions = INP 300ms → 150ms
- [ ] Use array functions instead of loops — 4–8 hours
- Built-in functions (C-level) beat userland PHP = 5–10x faster
- Expected gain: 0.5–1.0s LCP, 100–150ms INP
- Result: ~60% of users now pass LCP (database optimization often delivers most immediate win)
Tier 2: Medium Effort (4–6 weeks)
Why this tier: Compound on Tier 1. Address remaining bottlenecks. ROI: ~33% additional LCP improvement.
- [ ] Implement AVIF + WebP + lazy loading — 1–2 weeks
- Images are 60–70% of LCP time = LCP 2.1s → 1.5s
- [ ] Defer non-critical JavaScript — 1–2 weeks
- Render-blocking JS delays LCP 300–500ms = LCP 1.5s → 1.2s
- [ ] Preload critical fonts (font-display: swap) — 1–2 days
- Font delay = 300ms = LCP 1.2s → 1.0s
- [ ] Use generators for large data imports — 1–2 weeks
- Speeds APIs + prevents memory spikes = TTFB 500ms → 200ms
- [ ] Move heavy observer logic to async queue — 1–2 weeks
- Observer chains block PHP = TTFB 1200ms → 900ms
- Expected gain: 1.0–1.5s LCP, 100–200ms INP
- Result: ~85% of users now pass LCP
Tier 3: Advanced (6–12 weeks)
Why this tier: Reaching ceiling. Diminishing returns. ROI: ~25% additional improvement.
- [ ] Migrate from Luma to Hyvä — 8–12 weeks
- Theme is 30–50% of TTFB = LCP 900ms → 500ms
- Very high effort, major decision
- [ ] Extract critical CSS — 1–2 weeks
- Render-blocking CSS = 100–200ms = LCP 900ms → 800ms
- [ ] Web workers for heavy JS — 1–2 weeks
- Off-thread computation = INP 100ms → 50ms (marginal)
- [ ] Full-page caching (personalized) — 2–4 weeks
- Cache rate 80% → 95% (marginal improvement)
- [ ] Streaming CSV imports — 1–2 weeks
- Bulk imports without memory = TTFB 900ms → 800ms (marginal)
- Expected gain: 2.0–3.0s LCP total, 150–250ms INP total
- Result: >95% of users pass CWV (excellent)
Staffing & Team Structure for CWV Optimization
Recommended team composition (by store size):
Small Store (< 1k SKUs, 1–2 developers)
- 1 Backend Developer (part-time): Extension audit, observer optimization, profiling
- 1 Frontend Developer (part-time): Image srcset, JS deferral, font optimization
- DevOps / Hosting Partner (hourly): Production mode, Varnish, Redis setup
- Total investment: 4–8 weeks, $10k–20k
Medium Store (1k–10k SKUs, 3–5 developers)
- 2 Backend Developers (full-time): Server optimization, observer chains, queue system
- 2 Frontend Developers (full-time): Image optimization, JS deferral, INP fixes
- 1 DevOps Engineer (full-time): Infrastructure, monitoring, CI/CD
- Total investment: 8–12 weeks, $40k–80k
Large Store (10k+ SKUs, 8+ developers)
- 2–3 Backend Developers (dedicated): Architecture, performance, queue systems
- 2–3 Frontend Developers (dedicated): Critical path analysis, code-splitting, INP
- 1–2 DevOps Engineers (dedicated): Varnish, Redis, monitoring, scaling
- 1 Solutions Architect (oversight): Strategy, trade-offs, vendor coordination
- QA Engineer (1 full-time): Regression testing, performance validation
- Total investment: 12–16 weeks, $100k–200k
Integration Partner (Alternative to In-House)
If team capacity is limited:
- Full-service CWV optimization (4–12 weeks)
- Typical cost: €20k–60k depending on store size
- Benefit: External expertise, proven methodology
- Risk: Less context about business requirements
- Best for: Large stores with tight timelines
Success factors:
- Clear ownership (someone accountable for each section)
- Cross-team communication (backend ↔ frontend ↔ DevOps)
- Regular sync meetings (weekly status, blockers)
- Dedicated time (can’t be "side project" alongside regular work)
Monitoring and ongoing optimization
A. Continuous Measurement
- Set up CrUX API integration (free via Google Search Console)
- Monitor real-user metrics (vs synthetic Lighthouse)
- Alert on regressions: INP >250ms, LCP >3s
Setup CrUX monitoring:
# Google Search Console API integration
curl -X POST https://www.googleapis.com/webpagetest/v1/runtest \
?url=https://yourstore.com \
&key=YOUR_API_KEY
# Parse results for CLS/INP/LCP trends
jq '.data.runs[0].firstView.metrics' results.json
B. When Vitals Regress
- New extension installed? Check it
- Database query slow? Profile with Blackfire or New Relic
- Traffic spike? May be caching issue (check Varnish hit rate)
C. Implementation owner: who monitors ongoing
| Task | Owner | Effort | Why This Owner |
|---|---|---|---|
| CrUX setup (Google Search Console) | DevOps + Marketing | 1–2 hours | Analytics integration |
| Lighthouse CI automation | DevOps + Frontend | 2–4 hours | CI/CD pipeline |
| Alert configuration | DevOps | 2 hours | Infrastructure monitoring |
| Regression detection | Frontend + DevOps | 1 hour (recurring) | Performance tracking |
| Root cause investigation | Backend + Frontend | 1–4 hours (as needed) | Depending on regression |
| Report to stakeholders | Architect + Product | 1 hour (monthly) | Business communication |
Coordination: DevOps triggers alerts → Backend/Frontend investigates → Product reports results
D. Measuring business impact
- Core Web Vitals score correlates with:
- Lower bounce rate (~10–15% reduction per vital passed)
- Higher conversion rate (~3–5% lift from faster LCP alone)
- Better SEO ranking (ranking boost for fast pages)
Final Results: From Failing to Passing
For a sense of what the far end of this work looks like on a live site, here is brocode.at itself — a production site on the same mobile audit configuration as Figure 1:

Figure 2 — brocode.at, mobile, Lighthouse 13.4.1, simulated throttling. Real report, captured 2026-08-23; JSON alongside it in media/.
100/100, LCP 1.9 s, TBT 0 ms, CLS 0.023. Two honest caveats before anyone treats that as a target: it is a content site rather than a storefront, so it carries no cart, no catalog and no payment scripts — a very different problem from the one this guide is about. And it is the result of the sustained optimisation described in the tradeoff section, not a default. It is included as an existence proof that the numbers are reachable on real infrastructure, not as a benchmark a Magento store should expect to match.
What changed:
- LCP: 4.2s → 2.1s (50% faster)
- INP: 310ms → 185ms (40% faster)
- CLS: 0.25 → 0.07 (72% better)
- Score: 39/100 → 86/100 (120% improvement)
Key optimizations applied:
1. ✓ Database indexes (TTFB: 1.2s → 0.8s)
2. ✓ Defer JavaScript (LCP: 3.8s → 2.3s)
3. ✓ Image optimization (LCP: 2.3s → 2.1s)
4. ✓ Font preload (FCP: 1.3s → 1.1s)
5. ✓ Observer optimization (INP: 250ms → 185ms)
6. ✓ Image dimensions (CLS: 0.25 → 0.07)
Time investment:
- Database & infrastructure: 8 hours
- Frontend optimization: 40 hours
- Image processing: 16 hours
- Testing & monitoring: 16 hours
- Total: 80 hours (~2 weeks, 1 developer)
Business impact:
- Page load 2 seconds faster
- Estimated conversion lift: +7% (from speed alone)
- SEO ranking improvement: +15–20 positions
- Bounce rate reduction: ~30%
Related reading
The deep dives — each takes one level deeper than this page frames it:
- Magento Theme Performance: Hyvä or Luma? — the Level 0 decision, and what "Hyvä is free now" actually covers.
- Magento TTFB: Varnish, Redis and the Backend Cost — Varnish, Redis, production mode, database indexes, and the PHP work behind the server response.
- Magento JavaScript Optimization: Audit and Defer — extension audit, deferral patterns and RequireJS.
- Magento LCP Images: Measure First, Then Convert — where the LCP time actually goes, AVIF and WebP conversion, and keeping the maintenance burden near zero.
- Magento Responsive Images and Preload — the template half of the same job: view.xml sizes, a srcset override that stops lazy-loading the LCP image, and preloading it properly.
- Magento Font Optimization: Self-Hosted, Non-Blocking — self-hosting, subsetting and
font-display, split out of the JavaScript article for length. - Magento INP and CLS: Fixing the Two Hard Vitals — long tasks, interaction latency, and the layout shifts nobody notices until a customer taps the wrong button.
Elsewhere on brocode.at:
- BroCode Image Optimizer — WebP & AVIF for Magento 2 — the module behind the Level 3 pipeline.
- Converting a Large Catalog Without Melting the Server — bulk conversion without a maintenance window.
- AVIF vs WebP for Magento: Measure Your Own Catalog — the format decision, benchmarked.
- No Template Changes: Serving WebP and AVIF with nginx — the server-side half of that pipeline.
- Magento Elasticsearch Tuning with Real Benchmarks — PLP payload size is an LCP problem before it is a search problem.
- Magento Root Cause Analysis: Five Whys That Stick — the discipline that stops you optimising the wrong layer.
- Magento 2 Incident Recovery — when a "performance problem" is really an outage in progress.
Sources & References
- web.dev: Core Web Vitals — metric definitions and current thresholds.
- Chrome UX Report — the field data Google ranks on, as opposed to lab scores.
- PageSpeed Insights and Lighthouse — the lab tools behind the figures here.
- web-vitals JavaScript library — for measuring real users rather than a lab.
- Adobe Commerce: performance best practices — Adobe’s own baseline.
- Hyvä — the Level 0 alternative to Luma discussed in theme selection.
- Raw Lighthouse JSON for both figures is committed alongside them in this article’s
media/folder.