Core Web Vitals Debugging — Finding CLS and LCP Causes with Chrome DevTools
When PageSpeed Insights shows "Performance 74," the first thought is "where do I even start?" — there are 20+ items listed beneath the score. Finding the exact element causing the score drop is the only way to fix things efficiently. This guide covers how to precisely track which element causes CLS shifts and which element delays LCP, using Chrome DevTools and a few essential tools.
Finding the CLS Source Element
When PageSpeed reports high CLS, you need to know which element moved and by how much. There are three reliable methods.
Method 1: PageSpeed Audit Items
At the bottom of the PageSpeed results page, look for the "Avoid large layout shifts"audit. It lists exactly which elements contribute to CLS:
// PageSpeed JSON response: layout-shift-elements audit
{
"id": "layout-shift-elements",
"details": {
"items": [
{
"node": {
"selector": "div.typewriter-container > span",
"snippet": "<span class=\"text-4xl font-bold\">"
},
"score": 0.341 // This element's CLS contribution
},
{
"node": {
"selector": "header > nav > div.auth-section",
"snippet": "<div class=\"flex items-center\">"
},
"score": 0.089
}
]
}
}The selector and snippet fields pinpoint the exact element. Higherscore values mean greater CLS contribution from that element.
Method 2: Performance Tab Layout Shift Events
Chrome DevTools Performance tab provides deeper analysis:
- Open Chrome DevTools, go to the Performance tab
- Click the record button, then reload the page
- Stop recording after the page fully loads
- Find pink "Layout Shift" markers in the timeline
- Click one — the Summary panel shows which elements moved (Moved from/to coordinates)
The Experience row in the Performance recording displays all Layout Shift events. Clicking each event reveals which node moved, from where, and to where— down to exact pixel coordinates. You can also distinguish whether CLS 0.5 came from many small shifts or one large shift.
Method 3: Console Performance Observer
Paste this into the browser console to monitor CLS in real time:
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.hadRecentInput) continue; // Ignore user-triggered shifts
console.log("Layout Shift:", entry.value.toFixed(4));
for (const source of entry.sources || []) {
console.log(" Element:", source.node?.nodeName,
source.node?.className);
console.log(" Moved:", source.previousRect, "→",
source.currentRect);
}
}
}).observe({ type: "layout-shift", buffered: true });Identifying the LCP Element
To improve LCP, you must first know what the LCP element actually is. It may not be what you expect.
// PageSpeed JSON: largest-contentful-paint-element audit
{
"id": "largest-contentful-paint-element",
"details": {
"items": [{
"node": {
"selector": "div.hero > img",
"snippet": "<img src=\"/images/hero.avif\" />"
}
}]
}
}
// Console alternative:
new PerformanceObserver((list) => {
const entries = list.getEntries();
const last = entries[entries.length - 1];
console.log("LCP Element:", last.element);
console.log("LCP Time:", last.startTime, "ms");
console.log("LCP Size:", last.size, "px²");
}).observe({ type: "largest-contentful-paint", buffered: true });LCP might not be an image. LCP is the "largest contentful element." If the hero image is small or appears late, a large text block could become the LCP element. This commonly happens when animation libraries like motion/react applyopacity:0 to images — Lighthouse skips hidden images and picks the next largest visible element instead.
Using the Performance Tab Effectively
The Performance tab timeline shows all Core Web Vitals events at a glance:
| Marker | Color | Meaning | Where to Find |
|---|---|---|---|
| FCP | Green | First Contentful Paint | Timings row, green marker |
| LCP | Green | Largest Contentful Paint | Timings row, LCP marker |
| Layout Shift | Pink | Layout shift event | Experience row, pink blocks |
| Long Task | Red striped | Main thread blocked 50ms+ | Main row, red striped blocks |
Performance Tab Tips
- Network throttling: Set to "Fast 3G" to simulate PageSpeed's mobile environment.
- CPU throttling: Settings gear icon, set CPU to 4x slowdown to simulate mobile devices.
- Enable Screenshots: Check "Screenshots" during recording to see the visual state at each moment — you can observe CLS happening frame by frame.
Analyzing SSR HTML with curl
The HTML you see in the browser is the result after JavaScript execution. To see theraw server-rendered HTML, use curl. This is the only way to catch issues like motion/react's opacity:0 — the browser cannot show this because JavaScript immediately removes it.
# Find opacity:0 in SSR HTML
curl -s https://example.com | grep -i "opacity"
# Result: <div style="opacity:0;transform:translateY(60px)">
# → motion/react's initial inline style!
# Check img tag attributes
curl -s https://example.com | grep -oP '<img[^>]+>'
# Result: <img src="/hero.avif" width="900" height="1118"
# fetchpriority="high">
# <img src="/service.webp"> ← loading="lazy" missing!
# Check preload links
curl -s https://example.com | grep 'rel="preload"'
# Result: <link rel="preload" href="/hero.avif" as="image">
# <link rel="preload" href="/service.webp" as="image">
# ↑ Below-fold image is being preloaded — bandwidth waste!What to Check with curl
- opacity:0 inline styles: Initial state injected by motion/react during SSR. Delays LCP.
- img loading attribute: Below-fold images should have
loading="lazy". - Unnecessary preloads: Only the hero image should be preloaded. Others waste bandwidth.
- font-display value: Check webfont CSS for
swapvsoptional.
Web Vitals Chrome Extension
Search "Web Vitals" in the Chrome Web Store and install Google's official extension. It displays real-time Core Web Vitals in the browser toolbar for the current page.
- Real-time CLS monitoring: CLS accumulates as you scroll and click. See exactly which interaction triggers layout shifts.
- LCP element highlight: Click the extension to highlight the current LCP element. It may not be the element you expect.
- All metrics at once: LCP, CLS, INP, FCP, and TTFB displayed simultaneously.
- Console logging: Enable "Console Logging" in the extension options for detailed DevTools output.
Debugging Checklist
When PageSpeed scores are low, follow this order:
When CLS Is High
- Check the
layout-shift-elementsaudit in PageSpeed — identify which element - Check if the element dynamically changes size (missing image width/height, font swap, dynamic content)
- Check if animations use top/left/width/height instead of transform
- Check the Experience row in Performance tab for Layout Shift timing
When LCP Is Slow
- Identify the LCP element from PageSpeed — determine if it is an image or text
- Use curl to check SSR HTML — look for opacity:0 on the LCP wrapper
- Check image size — verify AVIF/WebP conversion, unnecessary dimensions
- Check preloads — remove preload from non-hero images
- Verify below-fold images have
loading="lazy"
When TBT Is High
- Find Long Tasks (50ms+) in the Performance tab — identify which script is responsible
- Check for third-party scripts (GA, GTM, ads)
- Check hydration time — too many React components slow down hydration
The debugging tools covered here are based on Chrome 120+. The Web Vitals extension is free on the Chrome Web Store. Once you identify the exact cause, apply the fix — whether that means removing opacity:0 traps, adding image dimensions, or deferring heavy scripts. Precise diagnosis leads to precise fixes.