Magento 2 Core Web Vitals: From Fails to Passes

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.

TermWhat it isGood
CWV — Core Web VitalsThe three metrics below taken together. Google uses them as a ranking signal.all three passing
LCP — Largest Contentful PaintHow 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 PaintThe 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 ShiftHow 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 ByteServer 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 PaintWhen anything at all first appears on screen. Always fires before LCP.≤ 1.8 s
TBT — Total Blocking TimeMain-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 DelayThe 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 InsightsGoogle’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".—
LighthouseThe 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 ReportField data from real Chrome users on a 28-day rolling window. This is what ranking actually uses.—
RUM — Real User MonitoringThe 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****

ScenarioCostRevenue GainDecision
Optimize DB indexes8 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 optimization2 weeks+5% conversion (€3.5k/year)✓ Usually worth it
Add Web Worker for analytics1 week+2% conversion (€1.4k/year)~ Maybe: tight margins?
Remove one extension2 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 + QUIC4 hours+1% conversion (€700/year)✗ Marginal gain for effort
Continuous monitoring & alerts€200/monthPrevents 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:

SectionOptimizationOwnerEffortDependencies
IITheme Selection (Luma vs Hyvä)DevOps + Backend Lead8–12 weeksMigration risk (highest)
III.A–BProduction Mode, Varnish, RedisDevOps / Infrastructure2–4 hoursServer access required
III.CDatabase Index OptimizationBackend + DevOps2–8 hoursSlow query log analysis + EXPLAIN
III.D–EProfiling & Monitoring SetupDevOps + Backend4–8 hoursDepends on tools (Blackfire, etc.)
IV.AExtension Audit & DisableBackend / Architect2–8 hoursTesting required
IV.BDefer Pixel Tracking (JS)Frontend + Marketing4–8 hoursPixel vendor coordination
IV.CjQuery/UI Compat Bug (if present)Frontend2–4 hoursCode review
V.A–CImage Optimization (AVIF/WebP via BroCode module)Backend + DevOps1–2 weeksImage processing pipeline + sidecar conversion
V.D–EResponsive Images (srcset)Frontend + Content1–2 weeksContent template updates
VI.A–BJavaScript Deferral (async/defer)Frontend1–2 weeksCritical path analysis
VI.C–DCode-Splitting & LoadJSFrontend2–4 weeksWebpack/bundler setup
VII.A–CFont Optimization (self-hosted)Frontend + DevOps2–4 daysCDN/static file setup
VII.D–EFont Preload & SubsettingFrontend1–2 daysNo dependencies
VIII.A–BINP: Observer Chain AuditBackend1–2 weeksProfiling + code review
VIII.CINP: Modal/Typeahead UXFrontend1–2 weeksJavaScript refactor
VIII.EINP: Web WorkersFrontend1–2 weeksBrowser API usage
IX.A–CCLS: Image DimensionsFrontend + Content2–4 daysTemplate updates
IX.DCLS: Message Queue (Async)Backend1–2 weeksQueue infrastructure
X–XIMonitoring & OngoingDevOps + BackendOngoingAlerting 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:

Lighthouse report for a stock Magento 2.4.8-p5 Luma install, scoring 96 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.

LevelAreaWhat it movesWhere
0Theme selectionEverything downstreamDeep dive: theme performance, Hyvä or Luma
1Server, caching, backend responseTTFB, and therefore LCP’s floorDeep dive: server and backend response time
2, 4, 5Extension audit, JavaScript deferral, fontsLCP and INPDeep dive: JavaScript and font strategy
3ImagesLCP, and CLS if dimensions are missingDeep dive: images and LCP
6, 7Interaction and layout stabilityINP and CLSDeep 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

TaskOwnerEffortWhy This Owner
CrUX setup (Google Search Console)DevOps + Marketing1–2 hoursAnalytics integration
Lighthouse CI automationDevOps + Frontend2–4 hoursCI/CD pipeline
Alert configurationDevOps2 hoursInfrastructure monitoring
Regression detectionFrontend + DevOps1 hour (recurring)Performance tracking
Root cause investigationBackend + Frontend1–4 hours (as needed)Depending on regression
Report to stakeholdersArchitect + Product1 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:

Lighthouse report for brocode.at scoring 100 on mobile

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:

Elsewhere on brocode.at:

Sources & References

Related Topics

← Previous
Next →

Written in collaboration with AI (Claude, by Anthropic). Ideas, verification, and accountability are mine; research and drafting are AI-assisted. Full disclosure → · Found an error? Tell me.