Analytics & Observability
See who visits, how fast it feels, what the code logged, and which deployment is live.
What it is
Web Analytics counts page views without cookies. Speed Insights measures how fast pages feel for real visitors. Both are a component in the root layout, and both are free on Hobby, pausing (never billing) at their limits: 50,000 analytics events a month, 10,000 Speed Insights events per 30 days. Runtime logs capture whatever functions print. And every git push becomes its own deployment with its own URL: branches get preview deployments, main becomes production.
When to use it
- Analytics: which pages people actually use
- Speed Insights: catching slow pages on real devices
- Logs: debugging what happened on a specific request
- Preview deployments: reviewing a change on a real URL before merging
1. This deployment (system environment variables)
Reading deployment info…
Open a pull request and its preview deployment shows preview here, with the branch name and a URL of its own.
2. Runtime logs
The button runs a Server Action that prints one JSON line. On Vercel it lands in the runtime logs within seconds.
From a terminal: vercel logs --environment production --since 10m, then look for the id.
3. Web Analytics and Speed Insights
<Analytics /> and <SpeedInsights /> are in app/layout.tsx, so every page reports. Their scripts load from this site's own /_vercel/ path, so the Content Security Policy needs no exceptions. Results appear in the project's Analytics and Speed Insights tabs.
Show code · src/app/layout.tsx
import { Analytics } from "@vercel/analytics/next";
import { SpeedInsights } from "@vercel/speed-insights/next";
import type { Metadata } from "next";
import { Suspense } from "react";
import { Nav } from "@/components/Nav";
import "./globals.css";
export const metadata: Metadata = {
title: "Vercel Feature Lab",
description: "One page per Vercel and Next.js feature, each with a live demo.",
};
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en">
<body>
<div className="shell">
<Nav />
<main className="content">{children}</main>
</div>
{/* Web Analytics (page views) and Speed Insights (real-user performance).
Both load their script from this site's /_vercel/ path, so the CSP
needs no exceptions. They read the URL's search params, which
Cache Components requires inside a Suspense boundary. */}
<Suspense fallback={null}>
<Analytics />
<SpeedInsights />
</Suspense>
</body>
</html>
);
}
Show code · src/lib/deployment.ts
// What Vercel tells a deployment about itself, through system environment
// variables. Every git push gets its own deployment and URL; these say which
// one is running.
export function deploymentInfo(env: Record<string, string | undefined> = process.env) {
const sha = env.VERCEL_GIT_COMMIT_SHA;
return {
environment: env.VERCEL_ENV ?? "local", // production | preview | development
branch: env.VERCEL_GIT_COMMIT_REF ?? null,
commit: sha ? sha.slice(0, 7) : null,
commitMessage: env.VERCEL_GIT_COMMIT_MESSAGE?.split("\n")[0].slice(0, 80) ?? null,
deploymentUrl: env.VERCEL_URL ? `https://${env.VERCEL_URL}` : null,
branchUrl: env.VERCEL_BRANCH_URL ? `https://${env.VERCEL_BRANCH_URL}` : null,
productionUrl: env.VERCEL_PROJECT_PRODUCTION_URL ? `https://${env.VERCEL_PROJECT_PRODUCTION_URL}` : null,
region: env.VERCEL_REGION ?? "local",
};
}
Show code · src/app/observability/actions.ts
"use server";
import { checkLimit, demoLimit, retryIn } from "@/lib/redis";
export type LogState = { id: string; at: string } | { error: string } | null;
// Anything a function writes to the console becomes a runtime log on Vercel.
// One JSON object per line keeps logs searchable: filter by the id shown on
// the page in the dashboard's Logs tab, or with `vercel logs`.
export async function writeLog(): Promise<LogState> {
const limit = await checkLimit(demoLimit);
if (limit && !limit.success) return { error: `Slow down: try again in ${retryIn(limit.reset)} s.` };
const id = `lab-${crypto.randomUUID().slice(0, 8)}`;
const at = new Date().toISOString();
console.log(JSON.stringify({ event: "lab.demo-log", id, at, region: process.env.VERCEL_REGION ?? "local" }));
return { id, at };
}