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.
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
One file. The dependencies already exist; you're wiring the components.
Requirements:
- Open
app/layout.tsx - Import
Analyticsfrom@vercel/analytics/next - Import
SpeedInsightsfrom@vercel/speed-insights/next - Mount both at the end of
<body>, after the footer - On
<SpeedInsights>, setsampleRate={0.25} - On
<Analytics>, pass abeforeSendfunction that returnsnullfor any event whoseurlincludes/admin(we don't have admin routes yet; this is preventive) - 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.
sampleRateis 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.beforeSendruns per event, client-side. Returningnulldrops the event entirely. Returning the event lets it through. You can also mutate the event and return a modified version, but for filtering,nullis the answer.- The
urlfield on the event is the page URL. If your filter is path-based, you can checkevent.url.includes('/admin')or usenew 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
FID 12ms
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 and look for requests to va.vercel-scripts.com. On regular pages you'll see the analytics beacons going out. On /admin/* paths, no beacons.
Done-When
app/layout.tsximports and renders<Analytics>and<SpeedInsights><SpeedInsights>hassampleRate={0.25}<Analytics>has abeforeSendthat drops/adminevents- Speed Insights dashboard shows sampled data within a few minutes of deployment
- Analytics dashboard shows visits but excludes any
/adminpaths
Troubleshooting
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?
You could, but Web Analytics is typically priced per event with a free tier per project, so for most teams the volume isn't the cost driver, keeping the data clean is. Speed Insights, on the other hand, is priced per data point and tends to fill faster, so sampling is the higher-leverage knob.
Solution
The finished app/layout.tsx is on the complete branch if you want to check your version against it.
import type { Metadata } from "next";
import Link from "next/link";
import { Analytics } from "@vercel/analytics/next";
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
beforeSend={(event) => {
if (event.url.includes("/admin")) return null;
return event;
}}
/>
<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 specific log levels you must not lose (errors, security events). 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.
Was this helpful?