Skip to content

Commit

Permalink
refactor: Migrated Next.js instrumentation into agent (#2409)
Browse files Browse the repository at this point in the history
  • Loading branch information
bizob2828 authored Jul 31, 2024
2 parents b9f64b7 + d0c3b96 commit 2aa18b5
Show file tree
Hide file tree
Showing 51 changed files with 2,157 additions and 20 deletions.
6 changes: 5 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ module.exports = {
parserOptions: {
ecmaVersion: 2022
},
ignorePatterns: ['test/versioned-external'],
ignorePatterns: [
'test/versioned-external',
'test/versioned/nextjs/app',
'test/versioned/nextjs/app-dir'
],
overrides: [
{
files: ['**/*.mjs'],
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,37 @@ If you cannot control how your program is run, you can load the `newrelic` modul
/* ... the rest of your program ... */
```

## Next.js instrumentation
**Note**: The minimum supported Next.js version is [12.0.9](https://github.com/vercel/next.js/releases/tag/v12.0.9). If you are using Next.js middleware the minimum supported version is [12.2.0](https://github.com/vercel/next.js/releases/tag/v12.2.0).

The New Relic Node.js agent provides instrumentation for Next.js The instrumentation provides telemetry for server-side rendering via [getServerSideProps](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props), [middleware](https://nextjs.org/docs/middleware), and New Relic transaction naming for both page and server requests. It does not provide any instrumentation for actions occurring during build or in client-side code. If you want telemetry data on actions occurring on the client (browser), you can [inject the browser agent](./documentation/nextjs/faqs/browser-agent.md).

Here are documents for more in-depth explanations about [transaction naming](./documentation/nextjs/transactions.md), and [segments/spans](./documentation/nextjs/segments-and-spans.md).


### Setup
Typically you are running a Next.js app with the `next` cli and you must load the agent via `NODE_OPTIONS`:

```sh
NODE_OPTIONS='-r newrelic' next start
```

If you are having trouble getting the `newrelic` package to instrument Next.js, take a look at our [FAQs](./documentation/nextjs/faqs/README.md).

### Next.js example projects
The following example applications show how to load the `newrelic` instrumentation, inject browser agent, and handle errors:

* [Pages Router example](https://github.com/newrelic/newrelic-node-examples/tree/58f760e828c45d90391bda3f66764d4420ba4990/nextjs-legacy)
* [App Router example](https://github.com/newrelic/newrelic-node-examples/tree/58f760e828c45d90391bda3f66764d4420ba4990/nextjs-app-router)

### Custom Next.js servers

If you are using next as a [custom server](https://nextjs.org/docs/advanced-features/custom-server), you're probably not running your application with the `next` CLI. In that scenario we recommend running the Next.js instrumentation as follows.

```sh
node -r newrelic your-program.js
```

## ECMAScript Modules

If your application is written with `import` and `export` statements in javascript, you are using [ES Modules](https://nodejs.org/api/esm.html#modules-ecmascript-modules) and must bootstrap the agent in a different way.
Expand Down
4 changes: 2 additions & 2 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1284,7 +1284,7 @@ This product includes source derived from [@aws-sdk/client-s3](https://github.co

### @aws-sdk/s3-request-presigner

This product includes source derived from [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3) ([v3.616.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.616.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.616.0/LICENSE):
This product includes source derived from [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3) ([v3.614.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.614.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.614.0/LICENSE):

```
Apache License
Expand Down Expand Up @@ -3110,7 +3110,7 @@ THE SOFTWARE.

### eslint-plugin-jsdoc

This product includes source derived from [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) ([v48.8.3](https://github.com/gajus/eslint-plugin-jsdoc/tree/v48.8.3)), distributed under the [BSD-3-Clause License](https://github.com/gajus/eslint-plugin-jsdoc/blob/v48.8.3/LICENSE):
This product includes source derived from [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) ([v48.7.0](https://github.com/gajus/eslint-plugin-jsdoc/tree/v48.7.0)), distributed under the [BSD-3-Clause License](https://github.com/gajus/eslint-plugin-jsdoc/blob/v48.7.0/LICENSE):

```
Copyright (c) 2018, Gajus Kuizinas (http://gajus.com/)
Expand Down
8 changes: 8 additions & 0 deletions documentation/nextjs/faqs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# FAQs

Are you having an issue with New Relic Next.js Instrumentation? Take a look at the following FAQS:

* [Deploying Next.js to Cloud Provider](./cloud-providers.md)
* [Injecting New Relic Browser Agent](./browser-agent.md)
* [Instrumenting 3rd Party Libraries](./instrument-third-party-libraries.md)
* [Error Handling](./error-handling.md)
12 changes: 12 additions & 0 deletions documentation/nextjs/faqs/browser-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Injecting Browser Agent

Q: How can I inject the [New Relic Browser Agent](https://docs.newrelic.com/docs/browser/browser-monitoring/installation/install-browser-monitoring-agent/) into a Next.js project?

A: It depends on if you are using the Pages or App Router for Next.js.


## Inject Browser Agent
The following links demonstrates how to inject the browser agent.

* [Pages Router](https://github.com/newrelic/newrelic-node-examples/blob/e118117470ae9f9038c60d8a171a6f0d440f6291/nextjs-legacy/pages/_document.jsx)
* [App Router](https://github.com/newrelic/newrelic-node-examples/blob/58f760e828c45d90391bda3f66764d4420ba4990/nextjs-app-router/app/layout.js)
61 changes: 61 additions & 0 deletions documentation/nextjs/faqs/cloud-providers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Deploy Next.js to Cloud Provider

Q: Can Next.js instrumentation work when deploying to [Vercel](https://vercel.com/frameworks/nextjs), [AWS Amplify](https://aws.amazon.com/amplify/), [Netlify](https://www.netlify.com/with/nextjs/), [Azure Static Sites](https://azure.microsoft.com/en-us/products/app-service/static), etc?

A: The short answer is no. Most of these cloud providers lack the ability to control run options to load the New Relic Node.js agent. Also, most of these cloud providers execute code in a Function as a Service(FaaS) environment. Our agent requires a different setup and then additional processes to load the telemetry. Our recommendation is to rely on OpenTelemetry and load the telemetry via our OTLP endpoint.

## OpenTelemetry setup with New Relic

To setup Next.js to load OpenTelemetry data to New Relic you must do the following:

1. Enable [experimental instrumentation hook](https://nextjs.org/docs/app/building-your-application/optimizing/open-telemetry). In your `next.config.js` add:

```js
{
experimental: {
instrumentationHook: true
}
}
```

2. Install OpenTelemetry packages.

```sh
npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http
```

3. Setup OpenTelemetry configuration in `new-relic-instrumentation.js`

```js
const { NodeSDK } = require('@opentelemetry/sdk-node')
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http')
const { Resource } = require('@opentelemetry/resources')
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions')
const { SimpleSpanProcessor } = require('@opentelemetry/sdk-trace-node')

const sdk = new NodeSDK({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'next-app',
}),
spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter({
url: 'https://otlp.nr-data.net',
headers: {
'api-key': process.env.NEW_RELIC_API_KEY
}
})),
instrumentations: [getNodeAutoInstrumentations()]
})
sdk.start()
```

4. Add the following to `instrumentation.ts` in the root of your Next.js project:

```js
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
require('./new-relic-instrumentation.js')
}
}
```

15 changes: 15 additions & 0 deletions documentation/nextjs/faqs/error-handling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Injecting Browser Agent

Q: How can I get the Next.js instrumentation to log errors to [New Relic Errors Inbox](https://docs.newrelic.com/docs/errors-inbox/errors-inbox/)?

A: The Node.js agent has an API to log errors `newrelic.noticeError`. Next.js has an error page that can be used to add the API call.


## Log errors to Errors Inbox

The error page varies between [Pages Router](https://nextjs.org/docs/pages/building-your-application/routing/custom-error) and [App Router](https://nextjs.org/docs/app/building-your-application/routing/error-handling) Next.js projects.


* [Pages Router](https://github.com/newrelic/newrelic-node-examples/blob/e118117470ae9f9038c60d8a171a6f0d440f6291/nextjs-legacy/pages/_error.jsx) error handling example.


23 changes: 23 additions & 0 deletions documentation/nextjs/faqs/instrument-third-party-libraries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Instrument 3rd Party Libraries within Next.js

Q: How can I get instrumentation to load for 3rd party libraries within my Next.js application like mysql, mongodb, pino, winston, etc?

A: Typically the New Relic Node.js agent auto-instruments all supported [3rd party libraries](https://docs.newrelic.com/docs/apm/agents/nodejs-agent/getting-started/compatibility-requirements-nodejs-agent/#instrument). Next.js, however, bundles your project and code spilts between server and client side via webpack. To get auto-instrumentation to work, you must externalize all libraries within webpack.

## Externalize 3rd party libraries in webpack

To externalize all supported 3rd party libraries, add the following to `next.config.js`:

```js
const nrExternals = require('newrelic/load-externals')

module.exports = {
// In order for newrelic to effectively instrument a Next.js application,
// the modules that newrelic supports should not be mangled by webpack. Thus,
// we need to "externalize" all of the modules that newrelic supports.
webpack: (config) => {
nrExternals(config)
return config
}
}
```
22 changes: 22 additions & 0 deletions documentation/nextjs/segments-and-spans.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Segments and spans

Segments and spans (when distributed tracing is enabled) are captured for Next.js middleware and `getServerSideProps`(Server-Side Rendering).

## Next.js middleware segments/spans

[Next.js middleware](https://nextjs.org/docs/middleware) was made stable in 12.2.0. As of v0.2.0 of `@newrelic/next`, it will only instrument Next.js middleware in versions greater than or equal to 12.2.0.

`/Nodejs/Middleware/Nextjs//middleware`

Since middleware executes for every request you will see the same span for every request if middleware is present even if you aren't executing any business logic for a given route. If you have middleware in a deeply nested application, segments and spans will be created for every unique middleware.

## Server-side rendering segments/spans

`/Nodejs/Nextjs/getServerSideProps/<Next.js page name>`

Next.js pages that contain server-side rendering must export a function called `getServerSideProps`. The function execution will be captured and an additional attribute will be added for the name of the page.

**Attributes**
| Name | Description |
| --------- | ---------------------------------------------------------- |
| next.page | Name of the page, including dynamic route where applicable |
39 changes: 39 additions & 0 deletions documentation/nextjs/transactions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Transactions

Transactions are captured as web transactions and named based on the Next.js page or API route. If you are using Next.js as a [custom server](https://nextjs.org/docs/advanced-features/custom-server), our Next.js instrumentation overrides the transaction naming of existing instrumentation for the custom server framework (for example, express, fastify, hapi, koa). Also, the transaction will be renamed based on the Next.js page or API route.

Let's say we have a Next.js app with the following application structure:

```
pages
index.js
dynamic
static.js
[id].js
api
hiya.js
dynamic
[id].js
```

The transactions will be named as follows:

| Request | Transaction Name |
| --------------------- | -------------------------------- |
| /pages/ | Nextjs/GET// |
| /pages/dynamic/static | Nextjs/GET//pages/dynamic/static |
| /pages/dynamic/example | Nextjs/GET//pages/dynamic/[id] |
| /api/hiya | Nextjs/GET//api/hiya |
| /api/dynamic/example | Nextjs/GET//api/dynamic/[id] |


## Errors
There are two exceptions to the transaction naming above.

### 404s
If a request to a non-existent page or API route is made, the transaction name will flow through the Next.js 404 page and will be named as `Nextjs/GET//404`.

### Non 404 errors
If a request is made that results in a 4xx or 5xx error, the transaction will flow through the Next.js error component and will be named as `Nextjs/GET//_error`.


Loading

0 comments on commit 2aa18b5

Please sign in to comment.