UNPKG

elastic-apm-node

Version:

The official Elastic APM agent for Node.js

86 lines (62 loc) 3.91 kB
# Next.js instrumentation dev notes Instrumenting the Next.js servers (the dev and prod servers) involves shimming a number of files. An overview of the instrumentation is provided here. Some of the complexity here is that I found the Next.js server internals not always ammenable to instrumentation. There isn't a clean separation between "determine the route", "run the handler for that route", "expose an error or the result". There are a number of ways to deploy (https://nextjs.org/docs/deployment) a Next.js app. This instrumentation works with "Self-Hosting", and using Next.js's built-in server. Here is the Next.js "server" class hierarchy: class Server (in base-server.ts) class NextNodeServer (in next-server.ts, used for `next start`) class DevServer (in dev/next-dev-server.js, used for `next dev`) ## dist/server/next.js This is the first module imported for `require('next')`. It is used solely to `agent.setFramework(...)`. Doing so in "next-server.js" can be too late because it is lazily imported when creating the Next server -- by which point metadata may have already been sent on the first APM agent intake request. ## dist/server/next-server.js This file in the "next" package implements the `NextNodeServer`, the Next.js "production" server used by `next start`. Most instrumentation is on this class. The Next.js server is a vanilla Node.js `http.createServer` (http-only, https termination isn't supported) using `NextNodeServer.handleRequest` as the request handler, so every request to the server is a call to that method. User routes are defined by files under "pages/". Generally, an incoming request path: GET /a-page is resolved to one of those pages: ./pages/a-page.js The user files under "./pages/" are loaded by `NextNodeServer.findPageComponents`. **We instrument `findPageComponents` to capture the resolved page name to use for the transaction name.** There are also other built-in routes to handle redirects, rewrites, static-file serving (e.g. `GET /favicon.ico -> ./public/favicon.ico`), and various internal `/_next/...` routes used by the Next.js client code for bundle loading, server-side generated page data, etc. At server start, a call to `.generateRoutes()` is called which returns a somewhat regular data structure with routing data. **We instrument *most* of these routes to set `transaction.name` appropriately for most of these internal routes.** A notable limitation is the `public folder catchall` route that could not be cleanly instrumented. An error in rendering a page results in `renderErrorToResponse(err)` being called to handle that error. **We instrument `renderErrorToResponse` to `apm.captureError()` those errors.** (Limitation: There are some edge cases where this method is not used to handle an exception. This instrumentation isn't capturing those.) *API* routes ("pages/api/...") are handled differently from other pages. The `catchAllRoute` route handler calls `handleApiRequest`, which calls `runApi`. **We instrument `runApi` to get the resolved (e.g. handling dynamic routes) route name.** (For next@11, `ensureApiPage` is instrumented instead because `runApi` was only added in next@12.) `runApi` loads the webpack-compiled user module for that route, and calls `apiResolver` in "api-utils/node.ts" to execute it. **We instrument that `apiResolver()` function to capture any errors in the user's handler.** ## dist/server/dev/next-dev-server.js This file defines the `DevServer` used by `next dev`. It subclasses `NextNodeServer`. The instrumentation in this file is **very** similar to that in "next-server.js". However, some of it needs to be repeated on the `DevServer` class to capture results specific to the dev-server. For example `DevServer.generateRoutes()` includes some additional routes. ## dist/server/api-utils/node.js See the `apiResolver()` mention above.