elliott.devPostsAbout

Debugging Next.js Applications

April 20, 2020

As a React meta-framework that executes in both Node.js and in the browser, is more complicated to debug than a typical browser-only React app.

I'll cover some different debugging techniques, each of which can be useful in different situations.

console.log

The classic technique that you can use to verify if and when a piece of code is executing, and log any values you're interested in.

Examples

let theme = props.theme;
// Basic usage
console.log('theme', theme);
// Indented JSON output with 2 spaces
console.log('theme', JSON.stringify(theme, undefined, 2));
// Human-readable output with colors
console.log('theme', require('util').inspect(theme, { colors: true }));

Using or can be useful to control the format of your logged values, for enhanced readability. The 'util' lib even works in the browser, thanks to .

More advanced console functions are also available, such as for tabular output, or to output to stderr instead of stdout.

Check stdout of your next dev process for server logs, and check your browser's JS console for client logs:

Server logs

Browser logs

Step-through debugging

It's often more effective to use a step-through debugger to pause and inspect your code as it executes. This is especially true when:

  • You have complex control flow and/or many variables, which makes it cumbersome to add console statements everywhere.
  • You want to know how a function is being called, by looking up and down the call stack.
  • You're not sure which values or functions you want to inspect prior to starting your app.

Browser-only debugging

To debug your Next.js app in the browser, simply:

  1. Start your app in "dev" mode, i.e. next dev, usually npm run dev.

  2. Open your app in your browser.

  3. Go to the "Sources" tab, then click on a line number to set a breakpoint:

    Chromium DevTools sources

From here, you can execute code in the JS console, navigate the call stack, and step through your code.

Source maps

Next.js has enabled by default in dev mode, so you'll see your uncompiled source code, and you can navigate to a specific source file in the sidebar, or by using the "Go to source" shortcut: Cmd+P on Chrome for macOS.

But sometimes you're debugging an issue with your compiled code, and the source code doesn't give you enough information to understand what's going on. For example, you want to run util.inspect, but util is not defined as a run-time name:

Source code with error: util is not defined

Luckily, you can disable source maps to view the compiled code that's actually executing. In Chromium-based browsers, go to your DevTools settings and uncheck "Enable JavaScript source maps":

Unchecked "Enable JavaScript source maps"

Then it becomes clear that webpack renamed the module at run time:

Compiled code

Server-only debugging

The browser is only half the story with Next.js apps. By default, the app is rendered on the server before being sent to the browser.

Some of this code is executed only on the server, so it's not possible to debug it in the browser at all, e.g. .

The Next.js server is fundamentally a Node.js process, so it can be debugged like any other Node.js process.

Node.js built-in debugger

The built-in debugger is probably the easiest to launch. First add a debugger; statement somewhere in your code, then:

node inspect ./node_modules/next/dist/bin/next

node inspect

Use commands like cont (shortcut c) to continue execution, exec() to evaluate an expression, or next (shortcut n) to step to the next line.

.

In situations where you only have command line access to the app you're debugging, the built-in debugger may be your only option.

Node.js inspector

node --inspect executes a program with a debug server, which listens on TCP port 9229, similar to a web server or a database server. You can connect to this server using one of several .

This enables you to use a full-featured UI to debug your app, much like debugging in the browser.

Usage:

node --inspect-brk ./node_modules/next/dist/bin/next
# or
node --inspect ./node_modules/next/dist/bin/next

Use --inspect-brk to pause your app immediately after starting, giving you the opportunity to debug code that executes at launch, and set new breakpoints before executing.

Use --inspect to run your app immediately. Execution will only pause after an inspector client connects and a breakpoint is hit.

Why ./node_modules/next/dist/bin/next? This is . On macOS or Linux using or , this is symlinked from ./node_modules/.bin/next, so node --inspect ./node_modules/.bin/next also works. But on Windows or using , ./node_modules/.bin/next is a shell script, so it can't be executed with node.

Node.js inspector via Chromium DevTools

Chromium-based browsers such as Chrome, Edge, and Brave come bundled with a Node.js inspector client. Go to and you should see your app. If you don't then click "Configure..." and make sure localhost:9229 is added as a target.

Chrome inspector device list

Click "inspect" and you'll see a familiar UI:

Chrome inspector

This works just like debugging your app in the browser.

Node.js inspector via VSCode

. This is a good option if you use VSCode as your editor and you want to debug and edit in the same context.

Create .vscode/launch.json if it does not exist, and add this config:

{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Attach to Remote",
"address": "localhost",
"port": 9229,
"sourceMaps": true
}
]
}

Then connect to your app by running this launch task, either from the "Run" tab (Shift+Cmd+D), or hit F5.

Set "sourceMaps": false to disable source maps.

.

Combined server + browser debugging via VSCode?

It's also possible to debug both server and client execution from a single VSCode launch command, using the .

package.json

{
"scripts": {
"debug": "node --inspect-brk ./node_modules/next/dist/bin/next"
}
}

.vscode/launch.json

{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
},
{
"type": "node",
"request": "launch",
"name": "Launch Next.js",
"runtimeExecutable": "npm",
"runtimeArgs": ["run-script", "debug"],
"port": 9229
}
],
"compounds": [
{
"name": "Debug Next.js + Chrome",
"configurations": ["Launch Next.js", "Launch Chrome"]
}
]
}

This can be convenient shortcut, but is not applicable in situations where:

  • You're debugging an issue in a non-Chrome browser.
  • Your server is running on another machine, or inside a Docker container.
  • You want to view network requests...

What about network/HTTP requests?

Unfortunately, the . This makes it harder to debug requests made by a Next.js server, which is a common scenario, e.g. .

An alternative approach is to use an HTTP debugging proxy that sits between your Next.js server and your API. I'll cover this in a future post :)

Conclusion

To be effective at debugging, it's important to understand your available tools, and how to use them. As with most aspects of programming, there are multiple options available, and each option has its own benefits and drawbacks in different situations. But often it comes down to preference.

In practice, I usually end up using Chromium DevTools. Given that it's the primary way I debug elements, scripts, and network requests in the browser, it's easier to become familiar with a single UI and set of keyboard shortcuts for Node.js debugging too.