---
title: "Observability Sampling"
description: "Wire up Web Analytics and Speed Insights on Saturday with sensible sampling rates and a beforeSend filter so observability data is useful without being exhaustive."
canonical_url: "https://vercel.com/academy/optimize-your-vercel-account/observability-sampling"
md_url: "https://vercel.com/academy/optimize-your-vercel-account/observability-sampling.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-10-28T18:12:53.837Z"
content_type: "lesson"
course: "optimize-your-vercel-account"
course_title: "Optimize Your Vercel Account"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Observability Sampling

# Observability Sampling

Speed Insights captures performance data from every page view. Web Analytics counts every visit. Drains forward every log line. The defaults are 100% sampling because the tools want to be useful out of the box. The defaults are also the most expensive way to run them.

You don't need a full sample to know if your Largest Contentful Paint is regressing. A 25% sample is enough to spot the trend, and it costs you a quarter as much. If you do have specific routes that don't deserve any tracking, the `beforeSend` callback in Web Analytics is the place to drop them.

For Saturday, we'll wire up both tools with sensible defaults. The shop doesn't have an admin section yet, but the filter pattern is what you'll keep coming back to as the app grows.

\*\*Note: What are Web Analytics and Speed Insights?\*\*

Two opt-in measurement products. Web Analytics counts visitors and page
views. Speed Insights collects Core Web Vitals from real sessions. Both are
billed by volume, events for one and data points for the other, which is why
sampling is the cost lever.

## Outcome

`<Analytics>` and `<SpeedInsights>` are mounted in the root layout. Speed Insights uses a 25% sample rate. Analytics filters out any future `/admin` routes before sending events.

## Hands-on exercise 4.4

Two files. The dependencies already exist; we're wiring the components, and the `beforeSend` filter gets a small Client Component of its own.

**Requirements:**

1. Create `app/analytics.tsx` and start it with `"use client"`
2. In it, import `Analytics` from `@vercel/analytics/next` and export an `Analytics` component that renders it with a `beforeSend` function that returns `null` for any event whose `url` includes `/admin` (we don't have admin routes yet; this is preventive)
3. Open `app/layout.tsx`
4. Import your `Analytics` from `./analytics`, and `SpeedInsights` from `@vercel/speed-insights/next`
5. Mount both at the end of `<body>`, after the footer
6. On `<SpeedInsights>`, set `sampleRate={0.25}`
7. In the Vercel dashboard, open **Analytics**, select Saturday, and click **Enable**
8. Commit, push, redeploy

**Implementation hints:**

- The components are inert during local development. They only send data when running on a Vercel-hosted deployment. So you won't see the integration "work" until after deploy.
- `sampleRate` is a number between 0 and 1. 0.25 means roughly one in four sessions get measured. That's enough granularity to surface real regressions but small enough to keep observability costs predictable.
- `beforeSend` is a function, and the root layout is a Server Component. Server Components can't hand functions to Client Components, so the filter lives in `app/analytics.tsx`. `sampleRate` is a plain number, so `<SpeedInsights>` stays in the layout.
- `beforeSend` runs per event, client-side. Returning `null` drops the event entirely. Returning the event lets it through. You can also mutate the event and return a modified version, but for filtering, `null` is the answer.
- The `url` field on the event is the page URL. If your filter is path-based, you can check `event.url.includes('/admin')` or use `new URL(event.url).pathname.startsWith('/admin')` for stricter matching.

## Try It

After the redeploy, open Saturday in a browser and navigate around. Then check the two dashboards.

For Speed Insights, open Saturday > Speed Insights. Within a few minutes you should see data appear. The sample rate means it'll take more total traffic to fill out the graphs than at 100% sampling, but the metrics shown will be representative.

```
Real Experience Score   84
Sampled visits today    12 (sampled from ~48 total)
LCP                     1.8s
INP                     64ms
CLS                     0.04
```

For Web Analytics, open Saturday > Analytics. You should see your visits counted in real time. The pages list should show `/`, `/drops`, `/products/foundry`, etc., but no `/admin` paths, even if you visit one in a browser. (Try `https://YOUR_DOMAIN/admin/test`. Next.js will 404, but more importantly, Analytics won't record the visit.)

To verify the filter is actually firing, open DevTools > Network, filter to Fetch/XHR, and look for the `view` request Web Analytics sends on each page. On regular pages you'll see the analytics beacons going out. On `/admin/*` paths, no beacons.

## Done-When

- [ ] `app/layout.tsx` imports and renders `<Analytics>` and `<SpeedInsights>`
- [ ] `<SpeedInsights>` has `sampleRate={0.25}`
- [ ] `<Analytics>` has a `beforeSend` that drops `/admin` events
- [ ] Speed Insights dashboard shows sampled data within a few minutes of deployment
- [ ] Analytics dashboard shows visits but excludes any `/admin` paths

## Troubleshooting

**The build fails with "Functions cannot be passed directly to Client Components."**

This is expected if `beforeSend` is written inside `app/layout.tsx`. The layout renders on the server, and a function can't cross from there into `<Analytics>`, which runs in the browser. Move the `<Analytics beforeSend={...} />` into `app/analytics.tsx` with `"use client"` at the top, and render your wrapper from the layout instead.

**The Analytics page offers an Enable button instead of data.**

This is expected if step 7 got skipped. Web Analytics only collects data once it's enabled for the project, and the next deployment adds the routes it uses. Click **Enable**, redeploy, and visit a few pages.

**Speed Insights shows zero visits.**

A few options. Sampling at 25% means low-traffic sites can take time to register their first sampled session, keep refreshing. Also confirm the SpeedInsights component is actually rendering: view the page source on production and look for the SpeedInsights script tag. If it's missing, the import path is wrong or the deploy didn't pick up the change.

**Analytics is counting `/admin` pages even though I set the filter.**

Two common issues. The `beforeSend` function must return the event (or a modified copy) for it to pass through; if you forget the return statement, all events get dropped silently. Also check that you're testing against a real Vercel deployment, not local dev, the integration is a no-op locally.

**Why not sample Analytics too?**

Web Analytics is priced per event, and its usage is counted across all projects on the team rather than receiving a separate allotment per project. The component doesn't expose a general sample-rate prop, so use `beforeSend` to drop routes you do not need and monitor the team's event usage. Speed Insights does expose `sampleRate`, making sampling the direct cost lever there.

## Solution

The finished [`app/analytics.tsx`](https://github.com/vercel/academy-starters/blob/main/optimize-your-vercel-account/complete/app/analytics.tsx) and [`app/layout.tsx`](https://github.com/vercel/academy-starters/blob/main/optimize-your-vercel-account/complete/app/layout.tsx) are in the `complete` folder if you want to check your versions against them.

```tsx title="app/analytics.tsx"
"use client";

import { Analytics as VercelAnalytics } from "@vercel/analytics/next";

// beforeSend is a function, so it has to live in a Client Component.
// The root layout is a Server Component and can't pass one down.
export function Analytics() {
  return (
    <VercelAnalytics
      beforeSend={(event) => {
        if (event.url.includes("/admin")) return null;
        return event;
      }}
    />
  );
}
```

```tsx title="app/layout.tsx"
import type { Metadata } from "next";
import Link from "next/link";
import { Analytics } from "./analytics";
import { SpeedInsights } from "@vercel/speed-insights/next";
import "./globals.css";

// ...metadata unchanged

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className="min-h-screen flex flex-col">
        {/* ...header, main, footer unchanged */}

        <Analytics />
        <SpeedInsights sampleRate={0.25} />
      </body>
    </html>
  );
}
```

A note on Vercel Drains for log shipping: if you're forwarding production logs to Datadog, Splunk, S3, or a custom HTTP endpoint, the Drains UI has a sampling field too. The default is 100%. The same logic applies, sample at 25-50% for general visibility, sample at 100% for the paths you must not lose, like `/api/drop-launch`. Sampling rules match on environment and request path prefix. To keep every error, add a second drain with a filter such as `log/statusCode ge 500` ([log drain sampling](https://vercel.com/docs/drains/reference/logs#sampling-rate)). Saturday doesn't ship logs externally yet, so we'll leave that for the day it does.

That's the last hands-on cost setting in the course. One more lesson in this section ties the whole cost arc together: instead of hunting these levers by hand, you'll point an agent skill at a real project and let it find them from production metrics.


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
