Next.js Analytics and Monitoring
Track page views, Web Vitals, and errors in Next.js with analytics and monitoring tools.
TL;DR
- Add Google Analytics to the root layout with the
GoogleAnalyticscomponent. - Report Core Web Vitals from the browser with
useReportWebVitals(). - Catch every server error in one place with
onRequestError().
Page Analytics
<GoogleAnalytics>Add Google Analytics to every page by rendering one component in the root layout.
import {
GoogleAnalytics,
} from '@next/third-parties/google';
// inside the root layout
<GoogleAnalytics gaId="G-XXXXXXX" />sendGAEvent()Record a custom event, such as a purchase, from a Client Component.
'use client';
import {
sendGAEvent,
} from '@next/third-parties/google';
<button
onClick={() => sendGAEvent('event', 'purchase')}
>
Buy
</button><Analytics>Track page views on Vercel by adding its analytics component to the layout.
import { Analytics } from '@vercel/analytics/next';
// inside the body of the root layout
<Analytics />Web Vitals
useReportWebVitals()Receive each Core Web Vitals metric as real visitors load your pages.
'use client';
import { useReportWebVitals } from 'next/web-vitals';
export function WebVitals() {
useReportWebVitals((metric) => {
console.log(metric.name, metric.value);
});
return null;
}sendBeacon()Send a metric to your server reliably, even as the page is closing.
useReportWebVitals((metric) => {
const body = JSON.stringify(metric);
navigator.sendBeacon('/api/vitals', body);
});Vitals EndpointReceive the metrics in a route handler and store or log them.
// app/api/vitals/route.ts
export async function POST(request: Request) {
const metric = await request.json();
console.log(metric.name, metric.value);
return new Response(null, { status: 204 });
}Server Monitoring
register()Run setup code once when the server starts by exporting this function.
// instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./monitoring/server');
}
}onRequestError()Report every uncaught server error from a single function.
export async function onRequestError(
err: unknown,
request: { path: string }
) {
const body = JSON.stringify({
message: String(err),
path: request.path,
});
await fetch(LOG_URL, { method: 'POST', body });
}registerOTel()Send traces to any OpenTelemetry backend with one call.
import { registerOTel } from '@vercel/otel';
export function register() {
registerOTel('my-app');
}Client Monitoring and Sentry
instrumentation-client.tsRun monitoring code in the browser before the app becomes interactive.
// instrumentation-client.ts
window.addEventListener('error', (event) => {
sendError(event.error);
});onRouterTransitionStart()Observe the start of every client-side navigation to time route changes.
export function onRouterTransitionStart(
url: string
) {
performance.mark(`nav-start-${url}`);
}@sentry/wizardSet up Sentry for client, server, and build with one command.
npx @sentry/wizard@latest -i nextjscaptureException()Report a handled error to Sentry and still let the caller see it.
import * as Sentry from '@sentry/nextjs';
try {
await chargeCard(order);
} catch (err) {
Sentry.captureException(err);
throw err;
}Tips
- Load analytics only in production by checking
process.env.NODE_ENV, so local testing does not pollute your reports. - Send the
error.digestvalue with every client error report, so you can match it to the server log entry.
Warnings
- A public analytics ID is fine in a
NEXT_PUBLIC_variable, but secret keys for monitoring tools must stay on the server. - The
instrumentation.tsfile runs in every server instance, so keepregister()fast and free of heavy startup work.
In Practice
Collect Core Web Vitals from real visitors and send them to your own endpoint.
- The component is a client file, because the hook runs in the browser.
- sendBeacon delivers the metric even if the visitor leaves the page.
- The component renders nothing, so it has no effect on the layout.
- Rendering it once in the root layout covers every page of the app.
// app/web-vitals.tsx
'use client';
import { useReportWebVitals } from 'next/web-vitals';
export function WebVitals() {
useReportWebVitals((metric) => {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
page: window.location.pathname,
});
navigator.sendBeacon('/api/vitals', body);
});
return null;
}
// app/layout.tsx (inside <body>)
// <WebVitals />
// {children}FAQ
Install @next/third-parties. Import GoogleAnalytics from @next/third-parties/google and render it in your root layout with your gaId.
Create a Client Component that calls useReportWebVitals() from next/web-vitals. Send each metric to your analytics endpoint and render the component in the root layout.
Run npx @sentry/wizard@latest -i nextjs. The wizard installs the SDK and creates the instrumentation files. It reports client errors, server errors, and performance traces.
It is a file in the project root that runs once when the server starts. Use its register() function to set up monitoring, and export onRequestError() to capture server errors.