Performance Optimization: Core Web Vitals
Purpose
This recipe optimizes a landing page to pass all three Core Web Vitals thresholds — LCP under 2.5 seconds, INP under 200 milliseconds, and CLS under 0.1. Covers image compression, lazy loading, CDN configuration, font optimization, JavaScript reduction, and mobile-specific techniques. Sites passing all CWV see 24% lower bounce rates and improved rankings. [src1]
Prerequisites
- Deployed landing page — Landing Page Platform Deployment
- Baseline performance measurement — run PageSpeed Insights and record current scores
- Code editing access — ability to modify HTML, CSS, and image assets
- Image source files — original uncompressed versions of hero images
- Chrome DevTools — for Lighthouse audits and performance profiling
Constraints
- CWV thresholds: LCP <= 2.5s, INP <= 200ms, CLS <= 0.1 (at 75th percentile over 28 days). [src2]
- Hero images must NOT be lazy-loaded — use
fetchpriority="high"for above-the-fold content. [src3] - Only 62% of mobile pages achieve good LCP; 43% fail the INP threshold. [src5]
- Images comprise 34% of average mobile page weight. [src3]
- 53% of mobile users abandon pages taking over 3 seconds. [src6]
Tool Selection Decision
Which optimization path?
├── Image-heavy with LCP > 2.5s
│ └── PATH A: Image Optimization First
├── JavaScript-heavy with INP > 200ms
│ └── PATH B: JavaScript Optimization
├── Layout shifts with CLS > 0.1
│ └── PATH C: Layout Stability
└── All metrics poor
└── PATH D: Full Optimization (A → C → B)
| Path | Focus | Typical Improvement | Time Required |
|---|---|---|---|
| A: Images | LCP + page weight | LCP -40-60%, weight -50% | 1-2 hours |
| B: JavaScript | INP + interactivity | INP -30-50% | 1-2 hours |
| C: Layout | CLS + stability | CLS to near 0 | 30-60 min |
| D: Full | All CWV metrics | PSI +20-40 points | 2-4 hours |
Execution Flow
Step 1: Run Baseline Audit
Duration: 10 minutes · Tool: PageSpeed Insights + Lighthouse
Run PageSpeed Insights on your URL and record baseline LCP, INP, CLS, total page weight, and number of requests. Use Lighthouse CLI for more detailed lab data.
Verify: Numeric values for all three CWV metrics recorded. · If failed: Use lab data from Lighthouse if field data unavailable.
Step 2: Optimize Images (Highest Impact for LCP)
Duration: 30-60 minutes · Tool: Squoosh, Sharp, or image CDN
Identify LCP element in DevTools Performance tab. Convert images to WebP/AVIF using Sharp or Squoosh. Implement responsive images with <picture> element and format fallback. Use fetchpriority="high" for hero images, loading="lazy" for below-fold images. Serve responsive sizes with srcset. [src3] [src7]
<!-- Hero: eager + high priority -->
<picture>
<source srcset="/images/hero.avif" type="image/avif">
<source srcset="/images/hero.webp" type="image/webp">
<img src="/images/hero.jpg" width="1200" height="630"
fetchpriority="high" decoding="async">
</picture>
<!-- Below fold: lazy load -->
<img src="/images/feature.webp" width="600" height="400"
loading="lazy" decoding="async">
Verify: LCP improved by 0.5-2s, page weight decreased 30-60%. · If failed: Check TTFB — server response may be the bottleneck. [src4]
Step 3: Fix Layout Shifts (CLS)
Duration: 20-30 minutes · Tool: Code editor
Add explicit width and height to all images, videos, and iframes. Preload fonts with font-display: swap. Reserve space for dynamic content with min-height. [src5]
Verify: CLS <= 0.1 in Lighthouse. No layout shift entries in Performance tab. · If failed: Check for late-loading third-party scripts injecting content.
Step 4: Configure CDN and Caching
Duration: 15-30 minutes · Tool: CDN provider or hosting platform
Set Cache-Control headers: max-age=31536000, immutable for static assets, max-age=0, must-revalidate for HTML. Add preconnect for third-party origins.
Verify: Response headers show correct Cache-Control. Second load is significantly faster. · If failed: Check for Vary headers preventing caching; purge CDN cache.
Step 5: Optimize JavaScript and CSS
Duration: 20-40 minutes · Tool: Code editor + bundler
Defer non-critical JavaScript. Inline critical CSS, preload full stylesheet. Delay third-party widgets until after page load. Break long tasks with setTimeout(resolve, 0) yields. [src5]
Verify: INP < 200ms. No long tasks in Performance tab. · If failed: Profile specific interactions to find blocking scripts.
Step 6: Run Final Audit and Document Results
Duration: 10 minutes · Tool: PageSpeed Insights
Record after metrics alongside before metrics. Verify all three CWV are in "Good" range. Document all optimizations applied.
Verify: PSI Performance score >= 90 on mobile. All CWV green. · If failed: Focus on the single worst remaining metric.
Output Schema
{
"output_type": "performance_optimized_page",
"format": "live URL + audit report",
"columns": [
{"name": "url", "type": "string", "description": "Landing page URL", "required": true},
{"name": "lcp_before", "type": "number", "description": "LCP before (seconds)", "required": true},
{"name": "lcp_after", "type": "number", "description": "LCP after (seconds)", "required": true},
{"name": "inp_before", "type": "number", "description": "INP before (ms)", "required": true},
{"name": "inp_after", "type": "number", "description": "INP after (ms)", "required": true},
{"name": "cls_before", "type": "number", "description": "CLS before", "required": true},
{"name": "cls_after", "type": "number", "description": "CLS after", "required": true},
{"name": "psi_score_before", "type": "number", "description": "PSI mobile before", "required": true},
{"name": "psi_score_after", "type": "number", "description": "PSI mobile after", "required": true}
],
"expected_row_count": "1",
"sort_order": "N/A",
"deduplication_key": "url"
}
Quality Benchmarks
| Quality Metric | Minimum Acceptable | Good | Excellent |
|---|---|---|---|
| LCP | <= 2.5s | <= 1.5s | <= 1.0s |
| INP | <= 200ms | <= 100ms | <= 50ms |
| CLS | <= 0.1 | <= 0.05 | <= 0.01 |
| PSI Mobile Score | >= 70 | >= 90 | >= 95 |
| Total Page Weight | < 1 MB | < 500 KB | < 200 KB |
| HTTP Requests | < 50 | < 30 | < 15 |
If below minimum: Focus on the single worst metric. LCP: optimize hero image and TTFB. INP: eliminate long JS tasks. CLS: add dimensions to media.
Error Handling
| Error | Likely Cause | Recovery Action |
|---|---|---|
| LCP worse after optimization | Hero image was lazy-loaded | Remove loading="lazy" from LCP element; add fetchpriority="high" |
| CLS increased after font changes | font-display: swap causes FOUT | Add size-adjust to @font-face; preload fonts |
| WebP/AVIF not serving | Server not configured for formats | Use <picture> element with fallback |
| PSI score varies between runs | Lab test variance (±5 points) | Run 3 times, take median; rely on field data |
| CDN not caching | Wrong Cache-Control headers | Check response headers; purge CDN cache |
| Third-party script blocking INP | Chat widget blocking main thread | Delay until after load event; use web worker |
Cost Breakdown
| Component | Free Tier | Paid Tier | At Scale |
|---|---|---|---|
| PageSpeed Insights | $0 | $0 | $0 |
| Lighthouse CLI | $0 | $0 | $0 |
| Image compression | $0 (Squoosh/Sharp) | $0 | $0 |
| Image CDN | $0 (Cloudinary free) | $89/mo | $224/mo |
| CDN (Cloudflare) | $0 | $20/mo Pro | $200/mo Business |
| Total | $0 | $0-20/mo | $89-224/mo |
Anti-Patterns
Wrong: Lazy-loading above-the-fold images
Adding loading="lazy" to the hero image delays LCP because the browser deprioritizes the image load. [src3]
Correct: Use fetchpriority="high" for the LCP element
Hero images should be eagerly loaded with fetchpriority="high". Only lazy-load below-the-fold images.
Wrong: Serving all images at full resolution
A 3000x2000 image is wasteful on a 375px mobile viewport. [src7]
Correct: Use responsive images with srcset and sizes
Serve different image sizes for different viewports using srcset and sizes attributes.
When This Matters
Use this recipe when the agent needs to improve landing page load speed, Core Web Vitals scores, or PageSpeed Insights scores. Requires a deployed page with code editing access. This recipe diagnoses and fixes performance issues.