Vercel ships two first-party measurement products that are easy to confuse: Web Analytics, which answers who visited which pages and what they did, and Speed Insights, which answers how fast those pages were for real users. Both are small client scripts served from your own deployment, both send to intake routes on that deployment, and both are switched on per project in the dashboard. They differ in what they measure, how they sample, what they cost to run, and which mistakes corrupt their numbers.
This article explains both from the data flow up. It covers how events travel from the browser to the dashboard, installation in a Next.js App Router project, the privacy model and how to redact sensitive URLs, custom events from the client and the server, what each Speed Insights metric means and how the Real Experience Score is built, a worked regression investigation, failure modes, and when to use something else. API names and limits are taken from Vercel's documentation as of October 2026; prices and quotas change often and are left out, so check the current limits page for your plan.
How the data flows
On every page, the Web Analytics script records a page view, including client-side route transitions, and any custom events you call. The Speed Insights script uses browser performance APIs to measure Core Web Vitals and sends them as data points. In version 2 of both packages, Vercel generates a random seed at build time and uses it to build the script and intake paths, a design it calls Resilient Intake; your deployment serves those paths, so there is no third-party domain in the page. The seed is passed to the package through a build-time configuration variable, VERCEL_OBSERVABILITY_CLIENT_CONFIG, which you normally leave alone.
Because the data travels through your own deployment, it inherits that deployment's routing and protection. That is convenient, and it is the root of two failure modes later in this article: projects sharing a domain sending data to each other, and protected preview deployments rejecting server-side events.
Installing both in Next.js
Turn the products on for the project in the Vercel dashboard, then install the packages and mount the components once in the root layout:
npm i @vercel/analytics @vercel/speed-insights// app/layout.tsx
import { Analytics } from '@vercel/analytics/next';
import { SpeedInsights } from '@vercel/speed-insights/next';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Analytics />
<SpeedInsights />
</body>
</html>
);
}The Next.js entry points detect the current dynamic route, such as /blog/[slug], so metrics aggregate per route rather than per URL. On Next.js older than 13.5, Vercel's docs say to import SpeedInsights from @vercel/speed-insights/react in a small client component and pass the pathname as the route prop. Other frameworks have their own entry points (/react, /remix, /sveltekit, /vue, /astro) or an inject function. Both packages turn on debug logging automatically when NODE_ENV is development or test, and the analytics package's automatic mode uses the same variable to choose between development and production. If your framework does not expose NODE_ENV, set the mode prop explicitly so local runs are not treated as production.
Privacy model and redaction
Web Analytics uses no third-party cookies. Visitors are identified by a hash created from the incoming request, and the visitor session is not stored permanently; it is discarded after 24 hours. Each data point may carry a timestamp, the URL and its dynamic path, the referrer, filtered query parameters, coarse geolocation, operating system, browser and device type. Unique-visitor numbers are therefore daily approximations, not identities, and they will not reconcile with a cookie-based tool's multi-day uniques.
The part you control is what the URL contains. Paths such as /invoice/12345 or query strings with tokens end up in analytics unless you rewrite them. Both packages take a beforeSend callback that runs in the browser before anything is sent; return a modified event to redact, or null to drop it:
'use client';
import { Analytics, type BeforeSendEvent } from '@vercel/analytics/next';
const ID = /\/(invoice|account|reset)\/[^/?#]+/g;
function redact(event: BeforeSendEvent) {
const url = new URL(event.url);
if (url.pathname.startsWith('/admin')) return null; // never send
url.pathname = url.pathname.replace(ID, '/$1/[id]'); // strip ids
url.searchParams.delete('token');
return { ...event, url: url.toString() };
}
export function AppAnalytics() {
return <Analytics beforeSend={redact} />;
}Passing a function as a prop requires a client component, which is why the callback lives in its own small file that the layout imports. Speed Insights accepts the same kind of beforeSend; apply the same redaction there, because its data points carry URLs too.
Custom events from client and server
Custom events, available on Pro and Enterprise plans, record actions that are not page views. Call track from @vercel/analytics in the browser, or from @vercel/analytics/server in route handlers and server actions:
// Client: a button handler
import { track } from '@vercel/analytics';
track('Signup', { location: 'footer', plan: 'pro' });
// Server: app/actions.ts
'use server';
import { track } from '@vercel/analytics/server';
export async function checkout(cartTotal: number) {
// ... charge the card first
await track('Checkout completed', { total_band: cartTotal > 100 ? 'over_100' : 'under_100' });
}The constraints shape your event design. Properties are flat: strings, numbers, booleans and null, no nested objects. Names, keys and values are limited to 255 characters each, and the number of properties per event depends on your plan. Prefer server-side events for anything that must be counted exactly, such as completed purchases, because client events can be lost when the user closes the tab or blocks scripts. Never put email addresses or user IDs in properties; bucket values, as the example does with the cart total, so events stay aggregate.
Speed Insights metrics and scoring
Speed Insights is real-user monitoring of Core Web Vitals. Vercel's documented targets, matching Google's thresholds, are:
| Metric | What it measures | Good |
|---|---|---|
| Largest Contentful Paint (LCP) | Time until the largest visible element renders | 2.5 s or less |
| Interaction to Next Paint (INP) | Delay from a user interaction to the next frame | 200 ms or less |
| Cumulative Layout Shift (CLS) | How much visible content moves unexpectedly | 0.1 or less |
| First Contentful Paint (FCP) | Time until the first DOM content renders | 1.8 s or less |
| Time to First Byte (TTFB) | Time until the first response byte arrives | under 800 ms |
INP replaced First Input Delay as the responsiveness metric, and Vercel has deprecated FID in Speed Insights. Each metric value is mapped to a 0 to 100 score using a log-normal curve derived from HTTP Archive data, and the Real Experience Score is a weighted average of those scores. Scores of 90 to 100 are good, 50 to 89 need improvement, and 0 to 49 are poor. The dashboard shows the 75th percentile by default, so a P75 LCP of 2.1 seconds means three quarters of measured page loads rendered their largest element within 2.1 seconds; switch to P90 or P99 to see the tail.
Data points come from hard navigations. TTFB and FCP are sent on load, LCP once the user interacts or leaves, and INP and CLS when the user leaves the page, so a visit yields between three and six data points. In a Next.js app that means mostly the first page of each session; client-side transitions after that are not separately measured. Keep that in mind before concluding that a route which users mostly reach by client navigation is fast.
Worked example: a product page regression
Suppose a deploy on Tuesday redesigns the product page, and on Thursday the Real Experience Score for /product/[slug] drops from 92 to 71 while other routes are unchanged. A useful sequence is:
- Filter Speed Insights to the route and split by device. The drop is entirely on mobile, where P75 LCP rose from 2.0 to 3.4 seconds and CLS from 0.03 to 0.19.
- Compare before and after the deployment. Both regressions start with Tuesday's deploy, and TTFB is unchanged, so the server is not the cause.
- Open the page on a throttled mobile profile. The new hero image is lazy-loaded and has no reserved dimensions, so it loads late and pushes the content down when it arrives.
- Fix it by loading the hero eagerly with a priority hint and giving it explicit width and height, deploy, and watch P75 LCP and CLS recover over the next day of traffic.
Web Analytics closes the loop: a custom event for add-to-cart, split by device, shows whether the mobile conversion rate moved with the vitals. Correlation is not proof, but a conversion dip that starts and ends with a vitals regression is a strong argument for performance budgets in review.
Cost matters at volume. A site with 200,000 visits a day at an average of four and a half data points per visit sends about 900,000 Speed Insights data points daily. Setting sampleRate to 0.25 on the component sends a quarter of them, about 225,000, which is still plenty for stable P75 values on busy routes, though rare routes get noisy. Sample less aggressively on low-traffic sites.
Failure modes
The common ways these numbers go wrong:
- Shared domains. When several projects sit behind one domain and only one serves the intake paths, every project's data lands in that project. Point each app's
eventEndpoint,viewEndpoint(analytics) orendpoint(Speed Insights) at its own deployment; the older analyticsendpointoption is deprecated in version 2. - Protected previews. Server-side
trackcalls on deployments with Vercel Authentication or password protection fail with 401. Create a Protection Bypass for Automation secret; the server package sends it automatically. - Client-only conversions. Purchase events sent only from the browser undercount whenever the tab closes first or the script is blocked.
- Leaky URLs. Without
beforeSend, identifiers in paths and query strings are collected, which can turn an aggregate tool into a personal-data processor. - Misreading samples. Real-user data reflects only visitors whose scripts loaded and ran. A drop in data points after a deploy can be a broken script rather than lost traffic.
- Missing route grouping. Without the framework entry point or the
routeprop, every product URL becomes its own row and no single row has enough data to trust.
When to use it, and what you trade
These products fit teams that deploy on Vercel and want privacy-preserving traffic numbers and field vitals with almost no setup. They are deliberately narrow: this is not a full product-analytics suite, and the data is tied to the Vercel project. If you need cross-platform product analytics, keep a dedicated tool; if you need traces from browser through API to database, use OpenTelemetry-based monitoring and treat Speed Insights as the vitals view. Web Analytics can be queried through Vercel's API and Speed Insights data can be exported through Drains, which are the routes to joining them with other data.
Further reading: real user monitoring explains field measurement in general, full-stack OpenTelemetry covers end-to-end tracing, Vercel Edge Functions covers moving work closer to users to cut TTFB, and edge compute compares edge platforms.
What to do next
- Enable Web Analytics and Speed Insights for the project, install both packages, and mount both components once in the root layout.
- Write a beforeSend that strips identifiers and tokens from URLs and drops private routes, and apply it to both products.
- List the three to five business actions you care about and send them as flat, bucketed custom events, server-side for anything financial.
- Read P75 LCP, INP and CLS per route and per device, and record a baseline before the next major deploy.
- Set a sampleRate based on traffic volume and the precision you need for your smallest important route.
- If several apps share a domain or previews are protected, configure per-app endpoints and the automation bypass secret.
- Add a performance budget check to code review and compare vitals before and after every deploy that touches layout or images.