Debugging Next.js Applications
April 20, 2020As a React meta-framework that executes in both Node.js and in the browser, Next.js 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 usageconsole.log('theme', theme);// Indented JSON output with 2 spacesconsole.log('theme', JSON.stringify(theme, undefined, 2));// Human-readable output with colorsconsole.log('theme', require('util').inspect(theme, { colors: true }));
Using JSON.stringify
or require('util').inspect
can be useful to control the format of your logged values, for enhanced readability. The 'util'
lib even works in the browser, thanks to webpack@4's built-in polyfills.
More advanced console functions are also available, such as console.table
for tabular output, or console.error
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:
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:
-
Start your app in "dev" mode, i.e.
next dev
, usuallynpm run dev
. -
Open your app in your browser.
-
Go to the "Sources" tab, then click on a line number to set a breakpoint:
From here, you can execute code in the JS console, navigate the call stack, and step through your code.
Source maps
Next.js has source maps 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:
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":
Then it becomes clear that webpack renamed the module at run time:
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. getServerSideProps
, getStaticProps
, and getStaticPaths
.
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
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 Inspector Clients.
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# ornode --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 Next.js's executable entry point. On macOS or Linux using npm or Yarn, this is symlinked from./node_modules/.bin/next
, sonode --inspect ./node_modules/.bin/next
also works. But on Windows or using pnpm,./node_modules/.bin/next
is a shell script, so it can't be executed withnode
.
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 chrome://inspect and you should see your app. If you don't then click "Configure..." and make sure localhost:9229
is added as a target.
Click "inspect" and you'll see a familiar UI:
This works just like debugging your app in the browser.
Node.js inspector via VSCode
VSCode also includes an inspector client. 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 Debugger for Chrome extension.
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 Node.js Inspector APIs do not yet support viewing network requests. This makes it harder to debug requests made by a Next.js server, which is a common scenario, e.g. resolving GraphQL requests during the server render.
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.