When a metric value alone doesn't tell you where in the code the problem lives
A bad LCP or CLS value on a monitoring dashboard tells you a problem exists, but not what's causing it. The attribution build of the web-vitals JavaScript library closes exactly that gap by reporting the concrete element or event responsible alongside every metric.
Inhaltsverzeichnis
- 1. The Problem: A Metric Value Without a Cause
- 2. What Is the Attribution Build of the web-vitals Library
- 3. LCP Attribution in Detail
- 4. CLS Attribution in Detail
- 5. Practical Debugging Example: A Bad LCP Value
- 6. Practical Debugging Example: A Bad CLS Value
- 7. Integration into Real User Monitoring
- 8. Sampling and Data Volume Considerations
- 9. Summary and Practical Recommendation
- 10. Zusammenfassung
- 11. FAQ
1. The Problem: A Metric Value Without a Cause
A typical real-user-monitoring dashboard shows that Largest Contentful Paint sits at 4.2 seconds at the 75th percentile on mobile devices, well above the recommended 2.5 second threshold. That number alone, however, says nothing about whether the problem is a slow-loading hero image, a delayed web font, or blocking JavaScript holding up the rendering process.
Without that information, a developer is left with tedious guesswork or manually reproducing the issue in local Chrome DevTools sessions, which often doesn't even work for production problems that only occur under specific network conditions or on specific devices. This is exactly where the attribution extension of the web-vitals library steps in, by building root cause analysis directly into the measurement taken in the user's actual browser.
2. What Is the Attribution Build of the web-vitals Library
Alongside its standard build, the web-vitals library maintained by Google offers a so-called attribution build, reachable under the import path web-vitals/attribution, which returns a structured attribution object in addition to the plain metric value. That object holds metric-specific details, for instance, for LCP a reference to the actual DOM element that was judged the largest visible element.
The extra overhead for this additional information is deliberately kept small, so the attribution build can be run in production without any noticeable impact on actual page performance. Under the hood, the library relies on existing browser APIs like PerformanceObserver and the Layout Instability API, but bundles their raw data into a much more usable structure.
3. LCP Attribution in Detail
For Largest Contentful Paint, the attribution object includes, among other fields, element with a CSS selector for the responsible element, url with the URL of the loaded resource if it's an image, and a breakdown of time spent across timeToFirstByte, resourceLoadDelay, resourceLoadDuration, and elementRenderDelay.
This breakdown allows for targeted diagnosis: a high resourceLoadDelay means the resource starts loading too late, which often points to missing preload hints. A conspicuously long resourceLoadDuration instead points to file size or server response time as the issue, while a high elementRenderDelay suggests blocking JavaScript or expensive style computation after the resource has already loaded.
import { onLCP, onCLS } from 'web-vitals/attribution';
onLCP((metric) => {
const { element, url, timeToFirstByte, resourceLoadDelay,
resourceLoadDuration, elementRenderDelay } = metric.attribution;
sendToAnalytics({
name: 'LCP',
value: metric.value,
element, // e.g. "img.hero-banner"
url, // resource URL, if applicable
timeToFirstByte,
resourceLoadDelay,
resourceLoadDuration,
elementRenderDelay,
navigationType: metric.navigationType,
});
});
onCLS((metric) => {
const { largestShiftTarget, largestShiftValue, largestShiftTime } =
metric.attribution;
sendToAnalytics({
name: 'CLS',
value: metric.value,
largestShiftTarget, // CSS selector of the shifted element
largestShiftValue,
largestShiftTime,
});
});
4. CLS Attribution in Detail
For Cumulative Layout Shift, the attribution object provides largestShiftTarget as a CSS selector of the element involved in the single largest layout shift, along with largestShiftValue for that individual shift's score contribution and largestShiftTime for the exact point in time within the page load.
Since CLS is often caused by several small shifts throughout a page's lifetime, focusing on the single largest shift is a deliberate trade-off between detail and data volume, but in practice it reliably surfaces the dominant culprit. That's often a late-loading ad banner, an image without a reserved aspect ratio, or a cookie consent banner inserted after the fact.
5. Practical Debugging Example: A Bad LCP Value
Suppose monitoring shows a bad LCP value for an online store's product detail page. The attribution data shows img.product-main-image as the element, a low timeToFirstByte, but a conspicuously high resourceLoadDelay of over eight hundred milliseconds, while the actual resourceLoadDuration looks unremarkable.
This combination suggests the main product image could actually be served quickly by the server, but the download start is being needlessly delayed, typically because the image only gets inserted into the DOM after blocking JavaScript runs, instead of appearing directly in the initial HTML. The fix is usually to render the image into the initial markup server-side and additionally request it early with <link rel="preload">.
6. Practical Debugging Example: A Bad CLS Value
In a second example, monitoring reports an elevated CLS value on a category page. Attribution shows div.newsletter-banner as the largestShiftTarget, with a largestShiftTime of roughly three seconds after page start, well after the initial rendering of visible content.
This pattern is typical of delayed, lazily loaded marketing components like newsletter banners or consent dialogs that get inserted into the existing page flow without reserved space, pushing visible content downward. The fix is usually reserving a fixed minimum height for the container from the start via CSS, so the later insertion no longer causes any shift, even if the component itself only gets populated later.
7. Integration into Real User Monitoring
For production use, attribution data is typically sent to an in-house analytics endpoint or a commercial RUM provider via the sendBeacon API or a fetch call with the keepalive flag, as soon as the browser finalizes the metric. It's important not to delay the send by doing heavy processing on the main thread, so the reporting itself doesn't add to the very performance burden being measured.
The collected attribution data can then be grouped by selector, page type, or device class to spot systematic patterns rather than one-off cases. If the same element selector keeps showing up as the leading cause of bad LCP values week after week, that's a reliable signal for where a targeted optimization is actually worth the effort, instead of investing resources based on guesswork.
8. Sampling and Data Volume Considerations
On high-traffic websites, sending attribution data for every single page view can generate a substantial data volume, both on the client side and at the analytics backend. A common approach is a sampling factor where, for example, only ten percent of page views send the full attribution payload, while the plain metric values continue to be captured for every view.
For critical page types like checkout, a higher sampling rate or even full capture often makes sense instead, since even small performance issues there can have a direct impact on conversion rate. The specific sampling strategy should be reviewed and adjusted regularly based on the actual data volume and the cost of the analytics backend.
9. Summary and Practical Recommendation
The attribution build of the web-vitals library turns abstract metric values into concretely actionable diagnostic information, by reporting the responsible element and relevant time breakdown alongside every measured metric. That significantly shortens the path from a flagged dashboard value to a targeted code change, without first having to painstakingly reproduce the issue manually.
To get started, it's worth enabling the attribution build with a moderate sampling factor on the most important page types first, and regularly scanning the collected data for recurring patterns. Over time, that builds a data-driven understanding of which concrete elements and loading phases are actually responsible for poor user experience in production.
| Metric | Attribution Field | Typical Cause | Diagnostic Hint |
|---|---|---|---|
| LCP | High resourceLoadDelay | Resource starts loading too late | Missing preload or late DOM insertion |
| LCP | High resourceLoadDuration | Large file or slow server | Check image compression, CDN, server time |
| LCP | High elementRenderDelay | Blocking JavaScript or CSS | Check render path and script execution |
| CLS | Late largestShiftTarget | Lazily loaded component without reserved space | Reserve a fixed minimum height via CSS |
Mironsoft
Web performance, Core Web Vitals, and load time optimization
Load times that don't make users bounce before the page is even visible?
We review existing websites for slow Core Web Vitals, bloated JavaScript bundles, and unnecessary render blockers, then build a performance foundation that stays measurable instead of just looking good once.
Performance Audit
Systematically measuring and fixing Core Web Vitals, load waterfall, and render blockers.
Bundle Optimization
Specifically reducing JavaScript and CSS bundle size and improving code splitting.
Monitoring Setup
Establishing continuous performance monitoring instead of a one-time snapshot.
10. Zusammenfassung
Web Vitals Attribution API
Technique
Attribution build of web-vitals reports cause, not just a number
Key fields
element, resourceLoadDelay, largestShiftTarget
Use case
Integration into real user monitoring via sendBeacon
Benefit
Root cause analysis directly in the real user's browser