Lesson 62 of 81
A Lighthouse score is one simulated page load on one emulated device. useReportWebVitals reports what actually happened on every real visitor's actual phone, network, and browser — a completely different, and often much less flattering, picture.
11 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
useReportWebVitals (from next/web-vitals) is a hook that fires a callback every time a Core Web Vital — LCP, CLS, INP, TTFB, FCP — is measured in an actual visitor's actual browser, during a real page load. This is Real User Monitoring (RUM): the data comes from the distribution of real devices, real network conditions, and real geographic locations your traffic actually has, not from one simulated run on one emulated profile.
That's the fundamental difference from a Lighthouse score. Lighthouse runs your page once (or a few times, averaged), on one configured device and network throttle, in a lab. It's useful for catching a regression before it ships, but it cannot tell you what the 20% of your visitors on a three-year-old Android phone over patchy 4G are actually experiencing — a page that scores 98 in Lighthouse can still have a genuinely bad real-world LCP for a meaningful slice of real traffic that a single synthetic run never represents.
// app/_components/web-vitals.tsx
"use client";
import { useReportWebVitals } from "next/web-vitals";
type ReportCallback = Parameters<typeof useReportWebVitals>[0];
const sendToAnalytics: ReportCallback = (metric) => {
const body = JSON.stringify(metric);
const url = "https://analytics.example.com/vitals";
if (navigator.sendBeacon) {
navigator.sendBeacon(url, body);
} else {
fetch(url, { body, method: "POST", keepalive: true });
}
};
export function WebVitals() {
useReportWebVitals(sendToAnalytics);
return null;
}
// app/layout.tsx
import { WebVitals } from "./_components/web-vitals";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<WebVitals />
{children}
</body>
</html>
);
}
sendToAnalytics is defined once, outside the component, as a stable module-level reference — useReportWebVitals re-invokes whatever function you pass it as new metrics arrive, and a different function identity on every render would report the same metric more than once. navigator.sendBeacon is preferred over a plain fetch here because a Web Vitals metric commonly fires right as the page is being hidden or unloaded (that's often exactly when the final LCP or CLS value is known) — a fetch can get cancelled mid-flight at that moment, while sendBeacon is specifically designed to survive it.
useReportWebVitals requires a Client Component, and the idiomatic pattern isolates that requirement to one tiny file. Putting "use client" on the whole root layout just to use this hook would pull far more than necessary into the client bundle — a dedicated component that does nothing but call the hook and return null keeps the boundary as small as it can possibly be.useReportWebVitals is documented to call whatever function reference you pass with the metrics available so far, so an unstable reference means duplicate reports rather than a clean one-metric-one-report relationship.navigator.sendBeacon exists specifically for this kind of "fire on unload" reporting. A metric that fires as the page is being hidden is exactly the scenario where a regular fetch call risks being cancelled before it completes — sendBeacon (with fetch({ keepalive: true }) as the fallback when it's unavailable) is built to survive that moment.The lesson on performance budgets and Lighthouse CI covers the lab-testing, pre-deploy half of performance measurement — catching a regression in CI before it ships to anyone. This lesson is the production half: what's actually happening to real visitors after a change has already gone out, across a spread of devices and networks no CI runner can fully stand in for.