Next.js is a modern JavaScript framework that optimizes the speed and performance of pages and applications, in addition to development speed and experience. It allows you to build hybrid or server-side rendered applications with minimal configuration. However, even Next.js applications need to be monitored for performance issues and errors. On the back end, you monitor application metrics—such as Apdex score, throughput, and transactions—to help scale projects. On the front end, you monitor browser-related metrics—such as the largest contentful paint, input delay, AJAX requests, or page views—for insight into performance and user behavior.

In this blog post, you’ll learn how to set up application performance monitoring for the server side and then browser monitoring for the front end, giving you full stack observability in your Next.js application. To start, you’ll need a New Relic account and license key, both available for free.

In this video, Mat walks through the full stack observability experience with Next.js applications in New Relic.

Backend observability

On the back end, Next.js uses Node.js and React for server-side rendering. In this blog post, we’ll go through the official New Relic Next.js instrumentation written in Node.js.

Installing the agent and middleware

Run the following command in your Next.js project to install the New Relic Node.js (APM) agent and New Relic middleware for Next.js.

npm install newrelic @newrelic/next

After the command completes successfully, you’ll see the dependencies included in your package.json file.

 "dependencies": {
   "@newrelic/next": "^0.3.0",
   "newrelic": "^9.0.0",
   "next": "latest",
   "react": "17.0.2",
   "react-dom": "17.0.2"
 },

Configuration

Next, modify your dev and start npm scripts by amending the scripts section of package.json file. Allow your application to run with Node’s -r option, which will preload @newrelic/next middleware.

NODE_OPTIONS='-r @newrelic/next' next start

The scripts section should looks something like this:

 "scripts": {
   "dev": "NODE_OPTIONS='-r @newrelic/next' next",
   "build": "next build",
   "start": "NODE_OPTIONS='-r @newrelic/next' next start",
   "lint": "next lint"
 },

If you’re using Next.js to run a custom server, add the -r option before your program runs:

   "dev": "NODE_OPTIONS='-r @newrelic/next' ts-node --project tsconfig.server.json server/index.ts",

Whether you’re using node or ts-node, you can add NODE_OPTIONS to the command in the exact same way.

Before you run your application, add the newrelic.js AMP agent configuration file to the root directory of your project. Remember to add app_name and license_key values to the file. For more information, see an example config file for your Next.js app.

Viewing performance data in New Relic

Run your application and go to the APM page in New Relic.  You’ll see your application’s server-side data flowing into New Relic.

New Relic UI - APM Node agent telemetry summary view, with visualizations of important performance metrics such as transaction time, Apdex score, throughput, and error rate.

Frontend observability

To monitor the front end of a Next.js application, you’ll need to inject the New Relic Browser agent. The agent uses a custom Document concept and the newrelic npm package you installed in the previous section. For more information, read our Docs about browser monitoring and the Node.js agent.

Configuration

The next code snippet, which you can also see in GitHub, shows the _document.tsx file. You can use it to update the HTML tags (like <html>, <body>) that are used to render a Next.js page. In this case, you need to modify the <head> tag of the document by injecting the New Relic browser agent script.

// pages/_document.tsx
const newrelic = require("newrelic");
import Document, {
 DocumentContext,
 DocumentInitialProps,
 Html,
 Head,
 Main,
 NextScript,
} from "next/document";
 
class MyDocument extends Document {
 static async getInitialProps(
   ctx: DocumentContext
 ): Promise<DocumentInitialProps> {
   const initialProps = await Document.getInitialProps(ctx);
 
   const browserTimingHeader = newrelic.getBrowserTimingHeader({
     hasToRemoveScriptWrapper: true,
   });
 
   return {
     ...initialProps,
     browserTimingHeader,
   };
 }
 
 render() {
   return (
     <Html>
       <Head>
         <script
           type="text/javascript"
           dangerouslySetInnerHTML={{ __html: this.props.browserTimingHeader }}
         />
       </Head>
       <body>
         <Main />
         <NextScript />
       </body>
     </Html>
   );
 }
}
 
export default MyDocument;

Here are the steps for this process:

  1. Install the newrelic npm package if you haven’t already with the npm install newrelic @newrelic/next command.
  2. Add the newrelic.getBrowserTimingHeader method inside the getInitialProps method..
    1. Pass hasToRemoveScriptWrapper: true as an argument to newrelic.getBrowserTimingHeader so that the browser script is returned without the <script> wrapper. See the node-newrelic docs for more details.
    2. Return the initial props from the getInitialProps method along with the browserTimingHeader script.
  3. In the render method, inject the New Relic Browser agent script to the <head> of the document.
  4. The _document.tsx(.js) file should be in the root of the pages directory of your project.

Viewing browser data in New Relic

Start the application and go to the browser monitoring page in New Relic to see client-side data from your application flowing into New Relic.

New Relic UI - Browser telemetry summary view, with visualization of important metrics such as core web vitals, user time on the page, load times, page views or throughput.

Sending detailed error information to New Relic

Send your application’s JavaScript error details to New Relic along with stack traces by utilizing the _error.tsx page described in the More Advanced Error Page Customizing section of Next.js documentation.

The following example, which you can also find on the GitHub page, shows how the _error.tsx file is customized to send error information to New Relic.

// pages/_error.tsx
function Error({ statusCode }) {
 return (
   <p>
     {statusCode
       ? `An error ${statusCode} occurred on server`
       : "An error occurred on client"}
   </p>
 );
}
 
Error.getInitialProps = ({ res, err }) => {
 if (typeof window == "undefined") {
   const newrelic = require("newrelic");
   newrelic.noticeError(err);
 } else {
   window.newrelic.noticeError(err);
 }
 
 const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
 return { statusCode };
};
 
export default Error;

The getInitialProps prototype of the error function is updated to capture both backend and frontend errors and send them to New Relic using the noticeError method.

Because _error.tsx can be run on both the server and the browser, you need a condition that specifies whether the code is executing in the server or client. This is the if (typeof window == "undefined")  condition, which means the code is running on the server side and the newrelic npm package should be required. Otherwise, you don’t need to worry about it because the client side already has the agent injected into it, as discussed in the previous section. Whether the code is run on the client or the server, the noticeError method will then pass the error along to New Relic as an argument.

Here’s an example of a stack trace for a server-side error displaying in New Relic:

New Relic UI - APM Node agent error details view, with stack trace and error attributes such as error code and message.

On the client side, since the New Relic browser agent attaches itself to the window object, the noticeError method is called on the window object.

Here’s an example of a front-end error and its stack trace displayed in New Relic:

New Relic UI - Browser agent JS error details view with stack trace and error statistics.

Putting your Next.js logs in context

New Relic supports automatic logs in context options. Log contexualization is important because it means sharing logs across related events, giving you additional context that can help you understand your application better. With the Node APM agent, you can automatically correlate and contextualize your Next.js application logs. Let’s look at the simplest way to forward logs to New Relic with the Winston framework. You can also look at other options in the Node.js: Configure logs in context documentation.

The first step is to update your newrelic.js config file with the following attributes:

application_logging: {
   forwarding: {
     enabled: true,
   },
 },

This tells the Node APM agent to forward all application logs and automatically link them to spans, traces, and other telemetry data. You can make additional adjustments to the configuration, too, such as limiting the maximum number of logs sent to New Relic.

You’ll also need to enable distributed tracing in the agent configuration:

 distributed_tracing: {
   /**
    * Enables/disables distributed tracing.
    *
    * @env NEW_RELIC_DISTRIBUTED_TRACING_ENABLED
    */
   enabled: true,
 },

The best way to integrate with the Winston logging framework is to write a simple component that can be used across your application in different files. It can look as simple as this Logger.tsx example.

// components/Logger.tsx
const winston = require("winston");
 
const logger = winston.createLogger({
 transports: [new winston.transports.Console()],
});
 
export { logger };

Next, import the component into your project where it’s needed:

import { logger } from "../components/Logger";

Include the log information you’re interested in.  For example, you could include it in your _document.tsx file.

   logger.info("NextJs New Relic redirecting to a page", {
     application: "NextJs NewRelic app logging",
     test: "Testing logging with Winston",
     pathname: ctx.pathname,
   });

Alternatively, you might want to gather information from a  subpage:

  logger.info("Getting post id", { postId: params.id });

Since Next.js is an isomorphic framework, it’s important to use the Winston logging framework in methods and files that are pre-rendered on the back end. In this example, the logger is added to the getServerSideProps method. (It can be also used inside getStaticPaths and getStaticProps methods). If used in the wrong places, the Next.js compilation process won’t work properly and you might experience build errors. These errors can happen when adding Node-only compatible NPM modules to pages rendered by browsers only. Browsers don’t have access to the Node API. They throw errors and stop the compilation process.

After adding Winston and log statements to your application, you can run the application and to see the results in New Relic under Logs:

New Relic UI - APM logs view

The next image shows automatic correlation between a transaction and logs:

New Relic UI - transaction and log correlation view.