Jake Teton‑Landis

Perfection enthusiast and byte craftsman splitting time between Miami, FL and New York, NY.

Interested in human productivity, big levers, and design.


Observing NextJS in Production

April 2021

NextJS offers a great developer experience and impressive performance, but has a number of frustrating limitations that make it difficult to deploy in production: There's no way to plug in performance monitoring like Datadog APM, or customize error handling to use a reporting system like Sentry, and logs and errors are printed to STDOUT/STDERR unstructured with no way to customize them.

The strategies for hosting NextJS apps in production basically boil down to:

  1. Make your site totally static, so that there's nothing to worry about. Use the static host of your choice. Give up all API features, previewing, regeneration, etc.
  2. Deploy with Vercel, but give up all control over builds and hosting. No option to use Datadog APM or other dependencies that require an "agent".
  3. Deploy manually with next start, but be blind to state of the system in production.
  4. Deploy manually and use a custom server entrypoint to add error handling and performance monitoring, but give up performance and features.

What we want

  1. A way to load Datadog APM dd-trace NodeJS package before importing NextJS, so that dd-trace can inject performance monitoring into lower level packages like http.
  2. A way to customizing logging so that logs can be parsed by our logging pipeline. Ideally, all logs would be JSON written to STDOUT or a UDP port.

Why not use a custom entrypoint?

The "escape hatch" is to use a "custom server" entrypoint instead of the recommended next start command, but this has significant drawbacks, as the docs warn:

Before deciding to use a custom server please keep in mind that it should only be used when the integrated router of Next.js can't meet your app requirements. A custom server will remove important performance optimizations, like serverless functions and Automatic Static Optimization.

Yikes; the whole reason we're using NextJS is for Automatic Static Optimization, so this is unacceptable.

Using preloading

The NodeJS CLI has an option to require a node module before importing and executing the entrypoint script: -r module / --require module. If we put our customization in a module that we preload before next start, we can work around many of NextJS's issues. We'll be able to use vanilla next start self-hosting, but get to have Datadog APM, error reporting, and customized logging.

1. Create your preload

Create a server-preload.js file in your NextJS project root. Here's a lightly-edited version of Notion's server-preload.js. Below you'll see how we set up Datadog tracing and fix the logging situation.

(Not seeing a big code block? Try refreshing, or click here to view the gist directly.)

2. Require your preload when you next start

Now you'll need to load your preload file when you start NextJS on the server. Everywhere you have next start, replace with:

node --require ./server-preload.js ./node_modules/.bin/next start
#    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#    Load our preload before running `next start`

You may beed to adjust paths to fit your app depending on your working directory where your app runs in production.

That's all there is to it!

Direct your questions/comments/concerns to @jitl on twitter.