Skip to content

Log all requests and responses in Next.js

Updated:

Table of contents

Open Table of contents

Intro

Next.js is great in many ways, but something that surprised me is the lack of request / response logs.

You start the Next.js server, it tells you everything is ready… and that’s it. Open a page, or make requests to the API if you have - nothing is logged!

Next.js server output

This is all you will see. Not very helpful...

If you deploy Next.js in Vercel, that is fine as they offer Runtime Logs as part of the platform1.

But what if you are deploying somewhere else? Like to your own Kubernetes cluster? You need to see what is happening with requests and responses, preferably in a standardized JSON format so it can be ingested and analyzed easily.

Some approaches

The problem here is that Next.js uses its own internal server and doesn’t expose the request / response objects.

A custom server would allow us to access the requests and responses, but it comes with downsides like losing automatic optimization of static pages, and added complexity. This is overkill just to have decent logging!

Using a middleware looks like a good alternative at first, until you realize you can only log requests this way, not responses, as the middleware doesn’t have access to the response object.

Hacking Next.js for the logs

It turns out the log-it-all solution means patching the Next.js server itself to be able to access the request and response objects. Not ideal, but let’s try to make it as clean and sustainable as possible.

1. Setup

In your package.json file, add a postinstall entry to scripts:

package.json
 "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "postinstall": "patch-package"
  },

Then install patch-package as a dev dependency (as it’s only needed at package install time), and pino-http as a regular dependency (as it will be used when the Next.js server is running).

terminal
npm install --save-dev patch-package 
npm install pino-http

2. Modify the Next.js server

Open node_modules/next/dist/server/lib/start-server.js in your favorite editor.

First, add the initialization of pino-http after _ispostpone (on line 41 as of Next.js 14.1.2):

node_modules/next/dist/server/lib/start-server.js
...
const _trace = require("../../trace");
const _ispostpone = require("./router-utils/is-postpone");
const _logger = require('pino-http')();
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : {
...

Now scroll further down, until you find the requestListener method (around line 134). At long last, we have direct access to the request and response here!

Time to log them. Add a call to the pino-http logger we just initialized, with the request and response.

node_modules/next/dist/server/lib/start-server.js
...
async function requestListener(req, res) {
    _logger(req, res);
    try {
        if (handlersPromise) {
...

The actual logging only happens after res.end is called by the framework, so we will get the full, completed response in the logs always.

3. Save the patch

Run patch-package on the next package to create a patch file with the changes we just did.

terminal
$ npx patch-package next
patch-package 8.0.0
 Creating temporary folder
 Installing [email protected] with npm
 Diffing your files with clean files
 Created file patches/next+14.1.2.patch

From now own, the patch will be auto-applied on npm install!

patch-package will try to apply the patch even if the version of Next.js changed, and in most cases it will succeed. But if it fails because of large changes in Next.js, now you know where to look at to fix it again :).

4. Enjoy proper logs

Start the Next.js server with next dev or next start and behold the glorious, detailed, JSON-formatted logs for requests and responses!

Next.js server output with logs

BONUS

Skipping some requests

It might be useful to skip some requests from being logged, like for static assets, otherwise the logs can become very noisy.

For this, we can use a regular expression before calling the logger:

node_modules/next/dist/server/lib/start-server.js
...
async function requestListener(req, res) {
    if (!/^(\/_next\/static|\/_next\/image|\/favicon.ico)/.test(req.url)) _logger(req, res);
    try {
        if (handlersPromise) {
...

Pretty print logs for development

The default JSON format of pino-http is minified so logs take as little space as possible. But they are hard to read in development.

We can use pino-pretty for a more friendly output:

terminal
npm install -g pino-pretty
npm run dev | pino-pretty

Next.js server output with pretty logs

Much nicer!

Example repo

You can check a working version of Next.js with full logging in this repo:

https://github.com/tomups/nextjs-pino-http

Feel free to copy the patch code to your codebase to save you from having to edit start-server.js yourself.


Footnotes

  1. This might actually be why Next.js doesn’t offer logging out of the box, to push you to use their parent platform!

  2. If you use yarn >= 2 or pnpm, you don’t really need patch-package as they support patching on their own.