Core Web Vitals & Page Performance
debt(d5/e5/b5/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list Lighthouse, PageSpeed Insights, WebPageTest, and CrUX — all specialist tools that must be explicitly run. Issues like render-blocking scripts or missing image dimensions are not caught by a compiler or default linter, and many problems (especially CLS and INP) only surface under real-world conditions. Not quite d7 because automated tooling (Lighthouse CI) can catch most issues in CI pipelines.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix describes a multi-step audit covering LCP, CLS, INP, and JS payload via code splitting. Individual fixes (adding async/defer, lazy loading, image dimensions) are one-liners, but the full remediation — preloading hero images, reducing JS payload with code splitting, eliminating layout shifts across templates — spans multiple files and components. Not e7 because it doesn't require cross-cutting architectural changes.
Closest to 'persistent productivity tax' (b5). applies_to is the general web context, meaning every page and every release needs to be evaluated against performance budgets. Large images, render-blocking scripts, and layout shifts are recurring risks that affect many work streams (images pipeline, JS bundling, CSS loading strategy, third-party scripts). Not b7 because the codebase isn't architecturally shaped by this concern — it's more of a recurring maintenance tax.
Closest to 'serious trap' (t7). The misconception field directly states the canonical wrong belief: developers assume fast server response (TTFB) guarantees good web performance, when in fact render-blocking scripts, large images, layout shifts, and JS execution time all independently affect user-perceived performance. This contradicts intuition from backend-focused developers and is a well-documented but widely-held misconception, justifying t7.
Also Known As
TL;DR
Explanation
Core Web Vitals (2024 set): LCP (Largest Contentful Paint, <2.5s good) — time until the largest image or text block renders; INP (Interaction to Next Paint, <200ms good, replaces FID) — responsiveness to user interactions; CLS (Cumulative Layout Shift, <0.1 good) — visual stability, unexpected layout movements. Optimising LCP: serve images in WebP/AVIF, use rel='preload' for hero images, server-side render critical content, use a CDN. Optimising INP: break up long tasks with scheduler.yield(), use web workers for heavy JS. Optimising CLS: always specify width/height on images and iframes, avoid inserting content above existing content. Measure with Lighthouse, PageSpeed Insights, or field data from Chrome UX Report. For PHP applications, server response time (TTFB) directly affects LCP.
Common Misconception
Why It Matters
Common Mistakes
- Render-blocking JavaScript in <head> without async or defer attributes.
- Large unoptimised images — the single biggest contributor to page weight.
- No lazy loading for below-the-fold images — loading=lazy is a one-attribute fix.
- Not measuring with real users (RUM) — synthetic tests miss real-world network and device conditions.
Code Examples
<!-- Render-blocking resources in <head>: -->
<head>
<script src="large-bundle.js"></script> <!-- Blocks rendering -->
<link rel="stylesheet" href="all-styles.css"> <!-- Blocks rendering -->
</head>
<!-- Non-blocking: -->
<script src="large-bundle.js" defer></script>
<link rel="preload" href="critical.css" as="style">
<!-- Preload critical resources -->
<link rel="preload" as="image" href="hero.webp" fetchpriority="high">
<link rel="preload" as="font" href="/fonts/sora.woff2" crossorigin>
<!-- Lazy-load below-fold images -->
<img src="product.webp" loading="lazy" decoding="async"
width="800" height="600" alt="Product photo">
<!-- Avoid layout shift — always specify dimensions -->
<img width="1200" height="630" src="banner.webp" alt="">
<!-- Defer non-critical JS -->
<script src="analytics.js" defer></script>
<script src="chatwidget.js" async></script>
<!-- Reduce INP: break up long tasks -->
// In JS:
await scheduler.yield(); // hand control back to browser between chunks