Skip to content

Performance and Core Web Vitals ​

A one-second delay in page load reduces conversions by 7% β€” performance is not a technical nice-to-have, it is a conversion lever.

Why This Matters ​

  • πŸ’» Dev: You own the performance budget. Every unoptimized image, render-blocking script, and layout shift is a conversion leak that only you can fix. Core Web Vitals are also a Google ranking factor β€” poor performance means less organic traffic.
  • πŸ“‹ PM: Performance directly impacts your conversion rate, bounce rate, and ad spend efficiency. A slow page burns paid traffic budget. You need to set performance targets and hold the team accountable.
  • 🎨 Designer: That hero video, custom font stack, and parallax animation all have a performance cost. Understanding the budget helps you make design choices that look great AND load fast.

The Concept (Simple) ​

Imagine visiting a restaurant.

LCP (Largest Contentful Paint) is how long it takes to get seated and see a menu. If you stand at the door for two minutes waiting, you might leave. On a landing page, LCP measures how quickly the main content appears. Target: under 2.5 seconds.

INP (Interaction to Next Paint) is how responsive the waiter is. You ask for water and they acknowledge you instantly versus staring blankly for five seconds. INP measures how fast the page responds when a visitor clicks a button or fills out a form. Target: under 200 milliseconds.

CLS (Cumulative Layout Shift) is the waiter bumping your table while you eat. You reach for your glass and it has moved. On a page, CLS happens when elements jump around as the page loads β€” a button shifts down just as you try to click it. Target: under 0.1.

If any of these experiences are bad, the visitor leaves before dessert β€” before they convert.


How It Works (Detailed) ​

The Three Core Web Vitals ​

╔══════════════════════════════════════════════════════════════════╗
β•‘                      CORE WEB VITALS                            β•‘
╠════════════════════╦═════════════════╦═══════════════════════════╣
β•‘        LCP         β•‘      INP        β•‘          CLS              β•‘
β•‘  Largest Content.  β•‘  Interaction to β•‘   Cumulative Layout       β•‘
β•‘  Paint             β•‘  Next Paint     β•‘   Shift                   β•‘
╠════════════════════╬═════════════════╬═══════════════════════════╣
β•‘  Good:   < 2.5s    β•‘  Good:  < 200ms β•‘  Good:   < 0.1            β•‘
β•‘  Needs:  2.5–4.0s  β•‘  Needs: 200–500 β•‘  Needs:  0.1–0.25         β•‘
β•‘  Poor:   > 4.0s    β•‘  Poor:  > 500ms β•‘  Poor:   > 0.25           β•‘
╠════════════════════╬═════════════════╬═══════════════════════════╣
β•‘  "Can I see the    β•‘  "Does it       β•‘  "Does stuff stop         β•‘
β•‘   content?"        β•‘   respond?"     β•‘   jumping around?"        β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•©β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•©β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

Page Load Waterfall ​

This is what happens when a visitor loads your landing page:

Time ──────────────────────────────────────────────────────▢

β”‚  DNS Lookup     β”‚
β”‚  β–ˆβ–ˆβ–ˆβ–ˆ           β”‚
β”‚                 β”‚
β”‚  TCP Connect    β”‚
β”‚     β–ˆβ–ˆβ–ˆβ–ˆ        β”‚
β”‚                 β”‚
β”‚  TLS Handshake  β”‚
β”‚        β–ˆβ–ˆβ–ˆβ–ˆ     β”‚
β”‚                 β”‚
β”‚  HTML Download  β”‚
β”‚           β–ˆβ–ˆβ–ˆ   β”‚
β”‚                 β”‚
β”‚  Parse HTML ─────────────────────────────────────────│
β”‚              β–ˆβ–ˆ                                      β”‚
β”‚                 β”‚                                    β”‚
β”‚  CSS Download   β”‚  ◀── Render-blocking!              β”‚
β”‚              β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ                                  β”‚
β”‚                 β”‚                                    β”‚
β”‚  JS Download    β”‚  ◀── Can be deferred               β”‚
β”‚              β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ                            β”‚
β”‚                 β”‚                                    β”‚
β”‚  Font Download  β”‚  ◀── Can cause CLS                 β”‚
β”‚                β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ                              β”‚
β”‚                 β”‚                                    β”‚
β”‚  Hero Image     β”‚  ◀── Usually the LCP element       β”‚
β”‚                β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ                        β”‚
β”‚                 β”‚           β”‚                        β”‚
β”‚                 β”‚           β–Ό                        β”‚
β”‚                 β”‚      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”‚
β”‚                 β”‚      β”‚   LCP   β”‚                   β”‚
β”‚                 β”‚      β”‚  EVENT  β”‚                   β”‚
β”‚                 β”‚      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β”‚
β”‚                 β”‚                                    β”‚
β”‚  JS Execute     β”‚  ◀── Affects INP                   β”‚
β”‚                    β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ              β”‚
β”‚                                                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

LCP: Making Content Appear Fast ​

The LCP element is usually your hero image, hero heading, or background video. Common causes of poor LCP:

ProblemFix
Large unoptimized hero imageUse WebP/AVIF, serve correct size via srcset
Render-blocking CSSInline critical CSS, defer the rest
Render-blocking JavaScriptUse defer or async attributes
Slow server response (TTFB)Use CDN, edge caching, static generation
Web fonts blocking renderUse font-display: swap, preload key fonts
Too many redirectsEliminate redirect chains

INP: Making Interactions Responsive ​

INP replaced FID (First Input Delay) as a Core Web Vital. It measures the worst interaction responsiveness across the entire page visit. Common fixes:

  • Break up long JavaScript tasks into smaller chunks using requestIdleCallback or setTimeout
  • Avoid layout thrashing β€” batch DOM reads and writes
  • Debounce scroll and resize handlers to reduce event frequency
  • Use CSS for animations instead of JavaScript β€” CSS animations run on the compositor thread
  • Lazy-load non-critical JavaScript with dynamic imports

CLS: Stopping the Page from Jumping ​

  WHAT CAUSES LAYOUT SHIFT:

  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚   [  Hero Image  ]   β”‚       β”‚   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ   β”‚
  β”‚   (no dimensions)    β”‚       β”‚   (with dimensions)  β”‚
  β”‚                      β”‚       β”‚                      β”‚
  β”‚   Loading...         β”‚       β”‚                      β”‚
  β”‚                      β”‚  ──▢  β”‚                      β”‚
  β”‚   β–Ό Content shifts   β”‚       β”‚   Content stays put  β”‚
  β”‚     down when image  β”‚       β”‚                      β”‚
  β”‚     loads            β”‚       β”‚   [  CTA Button  ]   β”‚
  β”‚                      β”‚       β”‚                      β”‚
  β”‚   [  CTA Button  ]   β”‚       β”‚                      β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       βœ— BAD CLS                      βœ“ GOOD CLS

Key fixes for CLS:

  • Always set explicit width and height on images (or use aspect-ratio in CSS)
  • Reserve space for ads, embeds, and dynamic content
  • Use font-display: optional or font-display: swap with size-adjusted fallback fonts
  • Avoid inserting content above existing content after page load
  • Use CSS contain to isolate layout changes

Optimization Techniques Summary ​

╔══════════════════════════════════════════════════════════════════╗
β•‘                   OPTIMIZATION CHECKLIST                        β•‘
╠════════════════════════════╦═════════════════════════════════════╣
β•‘  IMAGES                    β•‘  FONTS                             β•‘
β•‘  ☐ Convert to WebP/AVIF   β•‘  ☐ font-display: swap              β•‘
β•‘  ☐ Use srcset for sizes   β•‘  ☐ Preload critical fonts          β•‘
β•‘  ☐ Lazy load below fold   β•‘  ☐ Subset to needed characters     β•‘
β•‘  ☐ Set width and height   β•‘  ☐ Limit to 2 font families        β•‘
β•‘  ☐ Use CDN for delivery   β•‘  ☐ Use size-adjust for fallbacks   β•‘
╠════════════════════════════╬═════════════════════════════════════╣
β•‘  SCRIPTS                   β•‘  CSS                               β•‘
β•‘  ☐ defer non-critical JS   β•‘  ☐ Inline critical CSS             β•‘
β•‘  ☐ Code-split by route    β•‘  ☐ Defer non-critical CSS           β•‘
β•‘  ☐ Remove unused code     β•‘  ☐ Purge unused styles              β•‘
β•‘  ☐ Compress with gzip/br  β•‘  ☐ Use CSS containment             β•‘
β•‘  ☐ Use dynamic imports    β•‘  ☐ Minimize reflows                 β•‘
╠════════════════════════════╬═════════════════════════════════════╣
β•‘  INFRASTRUCTURE            β•‘  MONITORING                        β•‘
β•‘  ☐ CDN for static assets  β•‘  ☐ Lighthouse CI in pipeline        β•‘
β•‘  ☐ Edge caching / SSG     β•‘  ☐ Real User Monitoring (RUM)       β•‘
β•‘  ☐ HTTP/2 or HTTP/3       β•‘  ☐ Web Vitals JS library            β•‘
β•‘  ☐ Brotli compression     β•‘  ☐ Performance budget alerts        β•‘
β•‘  ☐ Preconnect to origins  β•‘  ☐ CrUX dashboard review            β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•©β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

Performance Budget ​

Set a budget and enforce it. Here is a starting point for a high-converting landing page:

Resource TypeBudgetWhy
Total page weight< 500 KBFast on 3G connections
HTML< 30 KBMinimal markup, semantic structure
CSS< 50 KBPurge unused, inline critical
JavaScript< 100 KBDefer everything non-essential
Images< 300 KBOptimize formats, lazy load
Fonts< 50 KBSubset, limit families
Third-party scripts< 50 KBAudit every tag, load async
LCP< 2.5sCore Web Vital target
INP< 200msCore Web Vital target
CLS< 0.1Core Web Vital target

Lighthouse Scoring Breakdown ​

Lighthouse weighs these metrics to produce a performance score from 0 to 100:

MetricWeight
First Contentful Paint (FCP)10%
Speed Index10%
Largest Contentful Paint25%
Total Blocking Time30%
Cumulative Layout Shift25%

A score of 90+ is considered good. For landing pages, aim for 95+ on mobile.


In Practice ​

Shopify: Optimizing for Mobile in Emerging Markets ​

Shopify recognized that many of their merchants' customers browse on low-end Android devices over 3G connections. Their landing page optimization work focused on:

  1. Aggressive image optimization β€” serving AVIF to supporting browsers, WebP as fallback, with srcset delivering appropriately-sized images for each device
  2. Eliminating render-blocking resources β€” inlining critical CSS and deferring all JavaScript
  3. Edge-side rendering β€” using their CDN to serve pre-rendered pages from locations close to visitors
  4. Reducing third-party impact β€” auditing every analytics and tracking script, removing those that did not justify their performance cost

The result: a 40% improvement in LCP on mobile devices, which correlated with a measurable increase in merchant sign-up conversion rates.

Notion: LCP Through Image Optimization ​

Notion's landing pages feature rich illustrations and screenshots. Their team tackled LCP by:

  • Preloading the hero image using <link rel="preload" as="image">
  • Serving illustrations as optimized SVGs instead of PNGs where possible
  • Using blur-up placeholder techniques for screenshots β€” a tiny base64-encoded placeholder appears instantly, replaced by the full image once loaded
  • Implementing responsive images with srcset so mobile visitors download smaller files

The lesson: Your LCP element is almost always an image. Identify it (Chrome DevTools highlights it), then apply every optimization: right format, right size, preloaded, and served from a CDN.


Key Takeaways ​

  • Core Web Vitals (LCP, INP, CLS) directly affect both conversions and Google search rankings
  • LCP is your most impactful metric β€” optimize your hero image and eliminate render-blocking resources
  • CLS is the easiest to fix β€” set dimensions on images and reserve space for dynamic content
  • INP matters most for pages with interactive elements like forms, calculators, or configurators
  • Set a performance budget (under 500 KB total page weight) and enforce it in your CI pipeline
  • Test on real devices over throttled connections, not just your fast laptop on WiFi
  • Use Lighthouse CI to catch regressions before they reach production

Action Items ​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ πŸ’» Dev   β”‚ ☐ Run Lighthouse on your current landing page and   β”‚
β”‚          β”‚   record baseline scores for all Core Web Vitals    β”‚
β”‚          β”‚ ☐ Identify your LCP element and optimize it β€”       β”‚
β”‚          β”‚   right format, preloaded, served from CDN          β”‚
β”‚          β”‚ ☐ Add the web-vitals JS library to capture Real     β”‚
β”‚          β”‚   User Monitoring data in production                β”‚
β”‚          β”‚ ☐ Set up Lighthouse CI in your deployment pipeline  β”‚
β”‚          β”‚   to block merges that exceed your performance      β”‚
β”‚          β”‚   budget                                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ πŸ“‹ PM    β”‚ ☐ Define performance targets: LCP < 2.5s,          β”‚
β”‚          β”‚   INP < 200ms, CLS < 0.1 as acceptance criteria     β”‚
β”‚          β”‚ ☐ Review your Google Search Console Core Web Vitals β”‚
β”‚          β”‚   report for current field data                     β”‚
β”‚          β”‚ ☐ Audit third-party scripts β€” does each one justify β”‚
β”‚          β”‚   its performance cost with measurable value?       β”‚
β”‚          β”‚ ☐ Add performance review to your landing page       β”‚
β”‚          β”‚   launch checklist                                  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 🎨 Designβ”‚ ☐ Understand the performance cost of your design    β”‚
β”‚          β”‚   choices β€” hero videos, custom fonts, animations   β”‚
β”‚          β”‚ ☐ Design with a 500 KB page weight budget in mind   β”‚
β”‚          β”‚   β€” ask "is this asset worth its bytes?"            β”‚
β”‚          β”‚ ☐ Provide fallback designs for slow connections β€”   β”‚
β”‚          β”‚   what does the page look like before images load?  β”‚
β”‚          β”‚ ☐ Limit font families to two and provide specific   β”‚
β”‚          β”‚   weights needed (do not load the entire family)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Next: Chapter 15: SEO and Structured Data

The Product Builder's Playbook