Speed (Web Vitals)

Measure LCP, INP, CLS, FCP and TTFB in the browsers of people visiting, per route and device, with numbers only and no session key in aggregate mode.

Speed measures how fast your pages feel to the people visiting them: Web Vitals, measured in their browsers, per route and device. It is off until you turn it on, works in both privacy modes, and runs from a small script that loads only on pages that ask for it.

Turn it on

Turn Speed on in the dashboard under Settings, Speed, or from a terminal. The sample rate keeps that percent of page views, 100% unless you choose less.

Terminal
littlefriend vitals enable --rate 50

Then add data-vitals to the lf.js tag. lf.js loads lf-vitals.js from the same place once the page is showing. Pages without the attribute load nothing extra.

In your HTML
<script defer src="https://cdn.littlefriend.io/lf.js"
  data-site="lf_YOUR_SITE_KEY"
  data-vitals></script>

With the npm package, call vitals() once after init. On the server it does nothing.

Once, when your app starts
import { init } from '@littlefriend/tracker';
import { vitals } from '@littlefriend/tracker/vitals';

init({ site: 'lf_YOUR_SITE_KEY' });
vitals();

While Speed is off, the collector drops what pages send, and data health counts it as vitals_off.

What it measures

MetricWhat it isGoodPoorCounts on
LCPLargest Contentful PaintWhen the largest image or block of text in the first screen finished rendering.2.5 s or lessover 4 sLanding route
INPInteraction to Next PaintHow long the page took to paint after a click, tap or key press: the slowest on the view, with one outlier ignored per 50 interactions.200 ms or lessover 500 msRoute showing
CLSCumulative Layout ShiftHow much the layout moved unexpectedly: the largest burst of shifts, each burst at most 5 seconds with gaps under 1 second.0.1 or lessover 0.25Route showing
FCPFirst Contentful PaintWhen the first text or image rendered.1.8 s or lessover 3 sLanding route
TTFBTime to First ByteWhen the first byte of the page arrived, from the start of the navigation.800 ms or lessover 1.8 sLanding route

Figures are the 75th percentile: three in four measured page views were at or under it. A figure is rated good, needs improvement or poor by the thresholds above, and a route by its worst Core Web Vital (LCP, INP and CLS). CLS comes from Chromium browsers only, so its counts are lower.

Which route a measurement counts on

  • LCP, FCP and TTFB happen while a page loads, so they count on the route the visitor landed on.
  • LCP, FCP and TTFB keep the landing route even when your app changes the route before lf-vitals.js loads, and LCP stays open across route changes the app makes on its own, until the first click, key press or hide.
  • INP and CLS keep changing while the page is open. A layout shift counts on the route showing when it happened, and an interaction on the route its last paint drew. In a single-page app, each route change ends one view and starts the next, so a slow click on the pricing page counts on the pricing page.
  • The click that opens a new route counts on the route it opened, where its last paint happens, even when the visitor holds the button down before letting go. A route change without a click, such as the back button, gives the new route no interaction of its own.
  • A page restored from the back-forward cache is a new view. Its LCP and FCP are the time from the restore to the next frame, and it has no TTFB.
  • Prerendered pages count from the moment they became visible. A tab opened in the background, or hidden before the page finished loading, sends no LCP or FCP: those would time the wait, not the page.

What is sent

One measurement per page view: a second after the route changes, or when the page is hidden. If the visitor comes back to the tab and the numbers change, the same measurement is sent again with a higher revision, and Little Friend keeps the latest, so the view still counts once. A view stops changing 12 hours after it started. When the visitor changes their consent, measuring starts over with a new view, so nothing measured after the change is linked to what was measured before it. It travels in the same batches as the page's other events.

  • The numbers, in whole milliseconds, and CLS as a score.
  • The route, with ids replaced, as every Little Friend event has it.
  • The device class, and a connection bucket: 4g, 3g, slow.

Never sent: selectors, element names, URLs, query strings or any text from the page. In aggregate mode no session key goes with a measurement. In journey mode a measurement belongs to its session, so the dashboard can link the slowest sessions to their replay. The collector clamps every number and drops a measurement with none left.

Reading the Speed page

  • Five figures at the 75th percentile, each with its rating, the good, needs improvement and poor split, and the change from the previous period.
  • Routes, sorted worst first, by views or by one metric. Pick a route to filter the page to it.
  • A device toggle for desktop, mobile and tablet. It combines with a route.
  • A figure from fewer than 20 views says Few views instead of a rating, and its route goes to the end of the worst first order.
  • In journey mode, the slowest page view of each session on the busiest routes, with a link to watch it. These come from page views kept as long as raw events.

Percentiles are read from hourly and daily histograms, so any range, route and device mix is exact to within one histogram step, and never an average of hourly figures. The traffic and platform filters do not apply to Speed, so the page does not show them. The same report is GET /api/projects/:id/reports/vitals, and the MCP server's get_vitals tool.