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.
Importante
Native Next.js OpenTelemetry support requires Node.js agent version 14.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.
The Node.js agent has had Next.js instrumentation since 2022 through @newrelic/next, and that instrumentation was bundled into the agent in 12.0.0. However, it was limited and didn't work when deploying to cloud providers like Vercel, AWS Amplify, Netlify, or Azure Static Web Apps. With the introduction of the hybrid agent, 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.
Enable the hybrid agent
To enable the hybrid agent and disable the agent instrumentations that conflict with Next.js, set the following configuration in newrelic.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 } }}Sugerencia
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:
NEW_RELIC_LICENSE_KEY=<your-license-key>NEW_RELIC_APP_NAME=<your-application-name>NEW_RELIC_OPENTELEMETRY_ENABLED=trueNEW_RELIC_INSTRUMENTATION_NEXT_ENABLED=falseNEW_RELIC_INSTRUMENTATION_HTTP_ENABLED=falseNEW_RELIC_INSTRUMENTATION_UNDICI_ENABLED=falseYou must also add an instrumentation.js file (or instrumentation.ts for TypeScript) to load the agent before the rest of your application:
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 for a more complete example of this setup.
Next.js instrumentation in 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 in the Vercel console under Environment Variables:
NEW_RELIC_LICENSE_KEY=<your-license-key>NEW_RELIC_APP_NAME=<your-application-name>NEW_RELIC_OPENTELEMETRY_ENABLED=trueNEW_RELIC_INSTRUMENTATION_NEXT_ENABLED=falseNEW_RELIC_INSTRUMENTATION_HTTP_ENABLED=falseNEW_RELIC_INSTRUMENTATION_UNDICI_ENABLED=falseYou 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 for a more complete example of this setup.
Inject the 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:
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 for a more complete example of this setup.