---
title: "Instrument Next.js applications | Grafana Cloud documentation"
description: "Get started with Frontend Observability for Next.js."
---

> For a curated documentation index, see [llms.txt](/llms.txt). For the complete documentation index, see [llms-full.txt](/llms-full.txt).

# Instrument Next.js applications

Follow these steps to get started quickly with the Faro Web SDK and Grafana Cloud Frontend Observability:

1. Create a Frontend Observability application
2. Set environment variables to configure the instrumentation
3. Install the Faro Web SDK
4. Instrument the Next.js backend
5. Enable backend correlation
6. Verify backend correlation is working
7. Observe the application in Frontend Observability
8. Upload source maps (optional)

The Faro Web SDK is a highly configurable open source real user monitoring (RUM) library built on OpenTelemetry and integrating seamlessly with Grafana Cloud and Grafana Frontend Observability.

Next.js is a popular hybrid framework that you can instrument for Real User Monitoring with Grafana Cloud. By monitoring your Next.js app with Frontend Observability, you can achieve the following:

- **Monitor the performance of your site**: collect performance KPIs such as Web Vitals automatically
- **Error capturing**: leverage the automatic error capturing on the browser side and get line-level insights on error
- **End-to-end observability**: automatically connect RUM sessions with APM and vice-versa for maximum insights on performance and root-causes

## Example code for this guide

You can find an example application that’s already fully customized at [https://github.com/grafana/faro-nextjs-example](https://github.com/grafana/faro-nextjs-example).

## Create a Frontend Observability application

To observe Real User Monitoring (RUM) data in Grafana Cloud Frontend Observability, create an application in the Grafana Cloud Frontend Observability plugin:

1. Sign in to Grafana Cloud, register for a Free Grafana Cloud account if required, to get to the [Grafana Cloud Portal page](/profile/org).
   
   If the account has access to multiple Grafana Cloud Organizations, select an organization from the top left organization drop-down.
   
   If the organization has access to multiple Grafana Cloud Stacks, navigate to a stack from the left side bar or the main Stacks list.
2. With a stack selected, or in the single stack scenario, below Manage your Grafana Cloud Stack, click **Launch** in the **Grafana** section:
3. Use the left navigation to expand **Frontend** and select **Frontend Apps**.
   
   If this is the first time you access Frontend Observability it displays the landing page. Click **Start observing** on the landing page to navigate to the overview page.
4. From the overview page, click **Create new** to create a Frontend Observability application to hold RUM data for your web application.
   
   To create an application requires the following information:
   
   - **App Name**: Give your app a meaningful name
   - **CORS Allowed Origin**: Domains allowed to access your Frontend Observability application, for local development set the value to `localhost`
   - **Default attributes**: Any attributes you’d like added to all signals, if unknown leave empty
5. From the **Web SDK Configuration** tab, copy the `url` value. You’ll use this in the next step when configuring environment variables.

## Set environment variables to configure the instrumentation

Provide the necessary environment variables to configure your application. Use the `url` value from the Frontend Observability application you created in the previous step.

The following table describes each environment variable:

Expand table

| Variable                         | Description                                                                                                              |
|----------------------------------|--------------------------------------------------------------------------------------------------------------------------|
| `NEXT_PUBLIC_FARO_URL`           | The URL of your Faro instance from **Frontend Observability** &gt; **Web SDK Configuration**                             |
| `NEXT_PUBLIC_FARO_APP_NAME`      | The name of your app, should be different for your backend and frontend                                                  |
| `NEXT_PUBLIC_FARO_APP_NAMESPACE` | The namespace of your app, should optimally be the same as the namespace of your backend                                 |
| `OTEL_EXPORTER_OTLP_ENDPOINT`    | The OTLP endpoint URL, example assumes the collector runs on the same machine                                            |
| `OTEL_EXPORTER_OTLP_PROTOCOL`    | The OTLP protocol, set to `http/protobuf`                                                                                |
| `OTEL_SERVICE_NAME`              | The backend service name                                                                                                 |
| `OTEL_RESOURCE_ATTRIBUTES`       | Resource attributes including `service.namespace`, set to the same value as the frontend namespace to enable correlation |

Example configuration:

![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```none
NEXT_PUBLIC_FARO_URL=my-url
NEXT_PUBLIC_FARO_APP_NAME=next-frontend
NEXT_PUBLIC_FARO_APP_NAMESPACE=nextjs-example

OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_SERVICE_NAME=next-backend
OTEL_RESOURCE_ATTRIBUTES=service.namespace=nextjs-example
```

## Install the Faro Web SDK

First, add the Faro Web SDK to your project. Install the Faro Web SDK package by running the following command for NPM:

sh ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sh
npm i @grafana/faro-web-sdk
```

Or the following command Yarn:

sh ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sh
yarn add @grafana/faro-web-sdk
```

Create a component in `components/frontend-observability.tsx` so you can inject the Web SDK into your Next.js frontend:

ts ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```ts
'use client';

import { faro, getWebInstrumentations, initializeFaro } from '@grafana/faro-web-sdk';
import { TracingInstrumentation } from '@grafana/faro-web-tracing';

export default function FrontendObservability() {
  // skip if already initialized
  if (faro.api) {
    return null;
  }

  try {
    const faro = initializeFaro({
      url: process.env.NEXT_PUBLIC_FARO_URL,
      app: {
        name: process.env.NEXT_PUBLIC_FARO_APP_NAME || 'unknown_service:webjs',
        namespace: process.env.NEXT_PUBLIC_FARO_APP_NAMESPACE || undefined,
        version: process.env.VERCEL_DEPLOYMENT_ID || '1.0.0',
        environment: process.env.NEXT_PUBLIC_VERCEL_ENV || 'development',
      },

      instrumentations: [
        // Mandatory, omits default instrumentations otherwise.
        ...getWebInstrumentations(),

        // Tracing package to get end-to-end visibility for HTTP requests.
        new TracingInstrumentation(),
      ],
    });
  } catch (e) {
    return null;
  }
  return null;
}
```

The Faro Web SDK captures data about your application’s health and performance and exports it to a managed Grafana Cloud endpoint.

Open source users can use the Grafana Alloy as their data collector. See the [Faro receiver with Grafana Alloy documentation](/docs/grafana-cloud/send-data/alloy/reference/components/faro/faro.receiver/) to learn more.

## Instrument the Next.js backend

To get the full picture, you should also instrument the backend parts of your Next.js app.

This allows you to correlate with backend activity from the frontend and create a better understanding and broader set of signals when investigating an issue.

Install the required packages:

Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```bash
npm install @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation
```

Create a new file `instrumentation.ts` in the root of your project. This file contains the configuration for the OpenTelemetry instrumentation for the Node.js backend of your app.

It contains a few defaults and a custom span processor to reduce the cardinality of span names.

ts ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```ts
import { Context } from '@opentelemetry/api';
import { ReadableSpan, Span, SpanProcessor } from '@opentelemetry/sdk-trace-node';
import { registerOTel } from '@vercel/otel';

/**
 * Span processor to reduce cardinality of span names.
 *
 * Customize with care!
 */
class SpanNameProcessor implements SpanProcessor {
  forceFlush(): Promise<void> {
    return Promise.resolve();
  }
  onStart(span: Span, parentContext: Context): void {
    if (span.name.startsWith('GET /_next/static')) {
      span.updateName('GET /_next/static');
    } else if (span.name.startsWith('GET /_next/data')) {
      span.updateName('GET /_next/data');
    }
  }
  onEnd(span: ReadableSpan): void {}
  shutdown(): Promise<void> {
    return Promise.resolve();
  }
}

export function register() {
  registerOTel({
    serviceName: process.env.OTEL_SERVICE_NAME || 'unknown_service:node',
    spanProcessors: ['auto', new SpanNameProcessor()],
  });
}
```

> Note
> 
> For even more insights, take a look at [`@vercel/otel`](https://github.com/vercel/otel/tree/main/packages/otel) to learn how you can include more instrumentation, such as automatic tracing for databases and other important software.

## Enable backend correlation

Create a `middleware.ts` file in the root of your app to enable correlation between frontend and backend telemetry. This middleware extracts the trace context from the active span and injects it into the response headers through the `server-timing` header, allowing Frontend Observability to match client-side performance KPIs with the exact backend requests.

typescript ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```typescript
import { NextRequest, NextResponse } from 'next/server';
import { trace } from '@opentelemetry/api';

export function middleware(request: NextRequest) {
  const response = NextResponse.next();
  const current = trace.getActiveSpan();

  // set server-timing header with traceparent
  if (current) {
    response.headers.set(
      'server-timing',
      `traceparent;desc="00-${current.spanContext().traceId}-${current.spanContext().spanId}-01"`
    );
  }
  return response;
}
```

## Enable Client service mapping in Application Observability

In your Grafana Cloud stack, [enable client services](/docs/grafana-cloud/monitor-applications/application-observability/manual/configure/#include-web-applications-and-mobile-devices). This ensures that frontend services such as your Next.js app are included in the service graph.

## Verify backend correlation is working

After implementing the middleware and backend instrumentation, verify that frontend and backend telemetry are properly correlated. A successful end-to-end trace contains both frontend spans (browser activity) and backend spans (server activity) linked by the same trace ID.

### Check the server-timing header

1. Open your Next.js application in a browser.
2. Open the browser’s Developer Tools (F12 or right-click &gt; Inspect).
3. Navigate to the **Network** tab.
4. Reload the page or navigate to trigger a request.
5. Select any request to your Next.js application.
6. In the **Headers** section, look for **Response Headers**.
7. Verify that a `server-timing` header is present and contains a `traceparent` value with a trace ID.
   
   Example:
   
   ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```none
   server-timing: traceparent;desc="00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
   ```
   
   The trace ID in this example is `4bf92f3577b34da6a3ce929d0e0e4736`.

If the `server-timing` header is missing or doesn’t contain `traceparent`, verify that:

- The `middleware.ts` file is in the root of your project
- OpenTelemetry is properly initialized in `instrumentation.ts`
- Your Next.js server has restarted after adding the middleware

### Verify end-to-end traces in Grafana Cloud

1. In Grafana Cloud, navigate to **Explore**.
2. Select **Tempo** as the data source (or another appropriate observability data source).
3. Search for recent traces from your application.
4. Open a trace and verify it contains both:
   
   - **Frontend spans**: Client-side activity such as `fetch` requests, navigation, or resource loading
   - **Backend spans**: Server-side activity such as `GET /`, `GET /api/*`, or database queries
   
   Both span types should share the same trace ID, confirming end-to-end correlation.

If traces only contain backend spans or only frontend spans:

- Verify the `TracingInstrumentation` is included in the Faro SDK configuration
- Check that `NEXT_PUBLIC_FARO_APP_NAMESPACE` matches `service.namespace` in `OTEL_RESOURCE_ATTRIBUTES`
- Confirm that client services are enabled in Application Observability settings

## Observe the application in Frontend Observability

Run your web application and make some requests to instrumented pages to send data to Grafana Cloud.

Learn how to explore your application in Frontend Observability with the documentation:

- [Overview your application’s performance](/docs/grafana-cloud/monitor-applications/frontend-observability/visualize-data/performance/)
- [Overview your application’s user sessions](/docs/grafana-cloud/monitor-applications/frontend-observability/visualize-data/sessions/)
- [Overview your application’s errors](/docs/grafana-cloud/monitor-applications/frontend-observability/visualize-data/errors/)
- [Browse advanced instrumentation techniques](/docs/grafana-cloud/monitor-applications/frontend-observability/instrument/)

## Upload source maps

Uploading source maps is optional but strongly recommended for production applications.

Source maps connect minified production code to original source files, making error stack traces readable in Frontend Observability. Without source maps, error messages and stack traces show minified code positions, making debugging difficult. If your application doesn’t minify code or you’re only testing in development, you can skip source map uploads.

Next.js applications use `Turbopack` for bundling, which requires a different approach for source map uploads compared to traditional bundlers. For Next.js applications, use the Faro CLI to upload source maps to Grafana Cloud.

> Note
> 
> If your Next.js application uses `webpack` instead of `Turbopack` and generates both client and server code, you can use the [Faro `webpack` plugin](/docs/grafana-cloud/monitor-applications/frontend-observability/configure/sourcemap-uploads/bundlers/#webpack-configuration) with the `nextjs: true` option. This adds `_next/` to the beginning of paths for proper stack trace matching.

### Configure source map uploads with the Faro CLI

The Faro CLI uploads source maps separately from your build process, making it ideal for Next.js applications and CI/CD pipelines.

Install the Faro CLI as a development dependency:

sh ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sh
npm install --save-dev @grafana/faro-cli
```

Upload source maps after building your application:

sh ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sh
npx faro-cli upload \
  --endpoint "$FARO_SOURCEMAP_API_URL" \
  --app-id "$FARO_APP_ID" \
  --api-key "$FARO_API_KEY" \
  --stack-id "$FARO_STACK_ID" \
  --bundle-id "$BUNDLE_ID" \
  --app-name "next-frontend" \
  --output-path "./.next/static" \
  --verbose
```

The `bundle-id` configuration identifies which source maps correspond to which deployed version of your application. This is required for Frontend Observability to match errors with the correct source maps. Refer to [bundle ID configuration](#bundle-id-configuration) for details on generating and using bundle IDs.

You can find the source map API endpoint, application ID, and stack ID in **Frontend Observability** &gt; **Settings** &gt; **Source Maps** &gt; **Configure source map uploads**.

For complete CLI configuration options and advanced usage, refer to [Upload source maps with the CLI](/docs/grafana-cloud/monitor-applications/frontend-observability/configure/sourcemap-uploads/cli/).

### Bundle ID configuration

The `bundleId` identifies which version of your application’s source maps to use when resolving error stack traces. Frontend Observability uses the bundle ID to match error stack traces with the correct source maps for that deployment.

You must configure the same bundle ID in three places:

1. **In your application code**: Set the bundle ID when initializing the Faro Web SDK
2. **In your build artifacts**: Inject the bundle ID into JavaScript files
3. **During source map upload**: Provide the bundle ID to the CLI

Generate a unique bundle ID for each deployment. Common approaches include:

**Using Git commit SHA:**

sh ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sh
BUNDLE_ID=$(git rev-parse --short HEAD)
```

**Using timestamp:**

sh ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sh
BUNDLE_ID=$(date +%s)
```

**Using CI/CD build number:**

For GitHub Actions:

sh ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sh
BUNDLE_ID=$GITHUB_RUN_ID
```

Configure the bundle ID in your Faro initialization code:

ts ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```ts
import { initializeFaro } from '@grafana/faro-web-sdk';

initializeFaro({
  url: process.env.NEXT_PUBLIC_FARO_URL,
  app: {
    name: process.env.NEXT_PUBLIC_FARO_APP_NAME,
    version: process.env.VERCEL_DEPLOYMENT_ID || '1.0.0',
    environment: process.env.NEXT_PUBLIC_VERCEL_ENV || 'development',
  },
  bundleId: process.env.NEXT_PUBLIC_BUNDLE_ID,
  // ... other configuration
});
```

Inject the bundle ID into your built JavaScript files:

sh ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sh
npx faro-cli inject-bundle-id \
  --bundle-id "$BUNDLE_ID" \
  --app-name "next-frontend" \
  --files "./.next/static/**/*.js" \
  --verbose
```

Upload source maps with the same bundle ID:

sh ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sh
npx faro-cli upload \
  --endpoint "$FARO_SOURCEMAP_API_URL" \
  --app-id "$FARO_APP_ID" \
  --api-key "$FARO_API_KEY" \
  --stack-id "$FARO_STACK_ID" \
  --bundle-id "$BUNDLE_ID" \
  --app-name "next-frontend" \
  --output-path "./.next/static" \
  --verbose
```

For complete documentation on source map uploads, refer to [Source map uploads](/docs/grafana-cloud/monitor-applications/frontend-observability/configure/sourcemap-uploads/).
