---
title: Next.js instrumentation with the Hybrid Agent
source: https://docs.newrelic.com/docs/apm/agents/nodejs-agent/extend-your-instrumentation/nextjs-instrumentation
---

The recommended way to monitor a Next.js application is to rely on the native OpenTelemetry spans emitted by Next.js together with the Node.js agent's Hybrid Agent feature, rather than the agent's built-in Next.js instrumentation. This page explains how to enable the Hybrid Agent, points to example applications, and covers common questions about injecting the browser agent and deploying to cloud providers.

> #### ⚠️ IMPORTANT
>
> Native Next.js OpenTelemetry support requires Node.js agent version [14.1.0](https://github.com/newrelic/node-newrelic/releases/tag/v14.1.0) or later. The Hybrid Agent only instruments the Node.js runtime; the Next.js edge runtime is not supported, which is why the `register()` example below exits early unless `NEXT_RUNTIME` is `nodejs`.
>
> In addition, we currently only support the Hybrid Agent approach for Next.js versions 16 and above.

The Node.js agent has had Next.js instrumentation since 2022 through [@newrelic/next](https://github.com/newrelic/newrelic-node-nextjs/releases/tag/v0.1.0), and that instrumentation was bundled into the agent in [12.0.0](https://github.com/newrelic/node-newrelic/releases/tag/v12.0.0). However, it was limited and didn't work when deploying to cloud providers like [Vercel](https://vercel.com/frameworks/nextjs), [AWS Amplify](https://aws.amazon.com/amplify/), [Netlify](https://www.netlify.com/with/nextjs/), or [Azure Static Web Apps](https://azure.microsoft.com/en-us/products/app-service/static). With the introduction of the [Hybrid Agent](https://docs.newrelic.com/docs/apm/agents/manage-apm-agents/opentelemetry-api-support), the Node.js agent can intercept OpenTelemetry spans and synthesize the telemetry that drives the New Relic experience. Native Next.js OpenTelemetry instrumentation was added in [14.1.0](https://github.com/newrelic/node-newrelic/releases/tag/v14.1.0).

## Enable the Hybrid Agent [#enable]

To enable the Hybrid Agent and disable the agent instrumentations that conflict with Next.js, set the following configuration in `newrelic.js`:

```js
'use strict'

exports.config = {
  app_name: ['Your application name'],
  license_key: 'your-license-key',
  opentelemetry: {
    enabled: true
  },
  instrumentation: {
    http: {
      enabled: false
    },
    next: {
      enabled: false
    },
    undici: {
      enabled: false
    }
  }
}
```

> #### 💡 TIP
>
> If you make native `fetch` calls, you must disable `undici` instrumentation as shown above. Next.js wraps `fetch` and creates its own client spans, so leaving `undici` enabled produces duplicate client spans in your traces.

If you prefer to use environment variables:

```ini
NEW_RELIC_LICENSE_KEY=<your-license-key>
NEW_RELIC_APP_NAME=<your-application-name>
NEW_RELIC_OPENTELEMETRY_ENABLED=true
NEW_RELIC_INSTRUMENTATION_NEXT_ENABLED=false
NEW_RELIC_INSTRUMENTATION_HTTP_ENABLED=false
NEW_RELIC_INSTRUMENTATION_UNDICI_ENABLED=false
```

You must also add an `instrumentation.js` file (or `instrumentation.ts` for TypeScript) to load the agent before the rest of your application:

```js
async function loadNewRelicAgent() {
  const { default: newrelic } = await import('newrelic')
  const agent = newrelic?.agent
  if (!agent || agent.collector?.isConnected?.()) {
    return
  }

  await new Promise((resolve) => {
    const done = () => {
      clearTimeout(timer)
      agent.removeListener('started', done)
      agent.removeListener('errored', done)
      resolve()
    }
    const timer = setTimeout(done, 8000)
    agent.once('started', done)
    agent.once('errored', done)
  })
}

export async function register() {
  // The agent only instruments the Node.js runtime, not the edge runtime.
  if (process.env.NEXT_RUNTIME !== 'nodejs') {
    return
  }

  await loadNewRelicAgent()
}
```

Check out the Next.js App Router [example application](https://github.com/newrelic/newrelic-node-examples/tree/9fa74c516926014fa33758cbd6e3737334ed4f98/nextjs/nextjs-app-router) for a more complete example of this setup.

## Next.js instrumentation in Vercel [#vercel]

Deploying to Vercel splits static and dynamic pages into different environments. Instead of relying on `.env` and `newrelic.js` to load agent configuration, define the following [environment variables](https://vercel.com/docs/environment-variables) in the Vercel console under **Environment Variables**:

```ini
NEW_RELIC_LICENSE_KEY=<your-license-key>
NEW_RELIC_APP_NAME=<your-application-name>
NEW_RELIC_OPENTELEMETRY_ENABLED=true
NEW_RELIC_INSTRUMENTATION_NEXT_ENABLED=false
NEW_RELIC_INSTRUMENTATION_HTTP_ENABLED=false
NEW_RELIC_INSTRUMENTATION_UNDICI_ENABLED=false
```

You still need the `instrumentation.js` file described above; only the source of the configuration changes on Vercel.

Check out the Next.js App Router [example application](https://github.com/newrelic/newrelic-node-examples/tree/9fa74c516926014fa33758cbd6e3737334ed4f98/nextjs/nextjs-app-router) for a more complete example of this setup.

## Inject the browser agent [#browser-agent]

Since Next.js is a full-stack framework, most customers want observability on both the client and the server. The following example shows how to inject the New Relic browser agent into every page.

Edit the root layout file within `app/` and add the following:

```js
import Script from 'next/script';

async function loadNewRelicAgent() {
  const { default: newrelic } = await import('newrelic')
  const agent = newrelic?.agent
  if (!agent || agent.collector?.isConnected?.()) {
    return newrelic
  }

  await new Promise((resolve) => {
    const done = () => {
      clearTimeout(timer)
      agent.removeListener('started', done)
      agent.removeListener('errored', done)
      resolve()
    }
    const timer = setTimeout(done, 8000)
    agent.once('started', done)
    agent.once('errored', done)
  })

  return newrelic
}

export default async function RootLayout({
  children,
}){
  // Wait for the agent to connect before requesting the browser timing header.
  const newrelic = await loadNewRelicAgent()
  const browserTimingHeader = newrelic.getBrowserTimingHeader({
    hasToRemoveScriptWrapper: true,
    allowTransactionlessInjection: true,
  })

  return (
    <html lang="en">
      <body className="min-h-full flex flex-col">{children}</body>
      <Script
        // Inline scripts require an id.
        // See https://nextjs.org/docs/app/building-your-application/optimizing/scripts#inline-scripts
        id='nr-browser-agent'
        // "beforeInteractive" loads the script before the page becomes interactive.
        strategy='beforeInteractive'
        // The script body is the browser timing header generated above. Because
        // `hasToRemoveScriptWrapper` is true, the header is raw JavaScript with no
        // surrounding <script> tag, so we inject it with `dangerouslySetInnerHTML`.
        dangerouslySetInnerHTML={{ __html: browserTimingHeader }}
      />
    </html>
  );
}
```

Check out the Next.js App Router [example application](https://github.com/newrelic/newrelic-node-examples/tree/9fa74c516926014fa33758cbd6e3737334ed4f98/nextjs/nextjs-app-router) for a more complete example of this setup.
