Debug Next.js apps with VS Code launch.json, next dev --inspect, browser DevTools and terminal logs, for both server and client code.

Bishal Biswas
Self Learner
Why debugging feels different in Next.js
In a plain React app, all your code runs in the browser, so the browser’s DevTools show you everything. A Next.js app is different: it is two programs at once.
- Server code runs in Node.js on your computer (or on a server later). Server Components, Server Actions, Route Handlers,
proxy.jsand everything inlib/live here. - Client code runs in the browser. Client Components (files with
'use client') live here, plus the server parts that React “hydrates” in the browser.
Think of a restaurant. The kitchen (the server) cooks the food, and the dining room (the browser) is where guests eat it. If the soup is cold, you need to know whether the cook made it wrong or the waiter left it on the counter. Debugging Next.js is mostly about knowing which room to look in.
In this lesson you will learn four tools, from simplest to most powerful:
console.logand where its output appears- The browser DevTools for client code
next dev --inspectfor server code- VS Code’s debugger, which can pause both sides
We use the Book Nook app you have been building. If you have not met the error overlay yet, read The Dev Server, Fast Refresh and Error Overlay first.
Step 1: know where console.log goes
Add a log to a Server Component and to a Client Component:
// app/books/[id]/page.js
import { getBook } from '@/lib/books';
import AddToCartButton from '@/components/AddToCartButton';
export default async function BookPage({ params }) {
const { id } = await params;
const book = await getBook(id);
console.log('Rendering book page for', id);
return (
<article>
<h1>{book.title}</h1>
<AddToCartButton book={book} />
</article>
);
}
// components/AddToCartButton.js
'use client';
export default function AddToCartButton({ book }) {
function handleClick() {
console.log('Clicked add to cart for', book.title);
}
return <button onClick={handleClick}>Add to cart</button>;
}
Run npm run dev and open http://localhost:3000/books/dune, then click the button.
Rendering book page for duneappears in your terminal, because the page ran on the server.Clicked add to cart for Duneappears in the browser console (press F12, then open Console), because the click handler ran in the browser.
GET /books/dune 200 in 1758ms
Rendering book page for dune
This is the first rule of Next.js debugging: if you cannot find your log, look in the other window.
Tip: In development, React also replays Server Component logs in the browser console with a small “Server” badge, so you may see them in both places. The terminal is still the real home of server output, and in production it is the only place they appear.
Step 2: debug client code with browser DevTools
For Client Components, the browser is your best friend. Open DevTools (F12, or Ctrl + Shift + I on Windows and Linux, Cmd + Option + I on macOS):
- Console shows logs and errors from the browser.
- Sources (Chrome and Edge) or Debugger (Firefox) lets you pause the code.
- Network shows every request, including Server Action calls and page data.
To pause code, add a debugger statement where you want to stop:
// components/AddToCartButton.js
'use client';
export default function AddToCartButton({ book }) {
function handleClick() {
debugger; // the browser pauses here while DevTools is open
console.log('Clicked add to cart for', book.title);
}
return <button onClick={handleClick}>Add to cart</button>;
}
Click the button with DevTools open. The page freezes on that line, and you can hover over book to see its value, or step forward one line at a time. A breakpoint is exactly this: a “stop here” sign in your code. You can also set one without editing files: press Ctrl + P (Cmd + P on macOS) in the Sources panel, type the file name, and click a line number.
Install the React Developer Tools browser extension too (react.dev links to it). Its Components tab shows your component tree with props and state, which is perfect for checking what your CartProvider holds right now.
Step 3: debug server code with –inspect
The browser cannot pause your Server Components, because they never run there. For that you start Next.js with the Node.js inspector, a built-in tool that lets a debugger connect to a running Node.js program.
npm run dev -- --inspect
The extra -- tells npm “pass the next flags to the script”, so this runs next dev --inspect. You will see a new line at the top:
> book-nook@0.1.0 dev
> next dev --inspect
Debugger listening on ws://127.0.0.1:9229/dbe2e504-2fc3-4f2b-a4cc-e3544c4d07c8
For help, see: https://nodejs.org/en/docs/inspector
▲ Next.js 16.4.3 (Turbopack)
- Local: http://localhost:3000
- Debugger port: 9229
✓ Ready in 471ms
Now connect a debugger:
- Chrome or Edge: open a new tab at
chrome://inspect(oredge://inspect), find your app under Remote Target, and click inspect. A separate DevTools window opens. Go to Sources. - Firefox: open
about:debugging, click This Firefox, and find your app under Remote Targets.
Put a debugger statement in a Server Component, refresh the page, and the server pauses on that line:
// app/books/[id]/page.js
export default async function BookPage({ params }) {
const { id } = await params;
const book = await getBook(id);
debugger; // the server pauses here when an inspector is attached
// ...
}
While paused, the browser tab keeps spinning, because the server has not finished the page yet. That is normal. Press the resume button (or F8) to let it continue.
Good to know: When the error overlay shows a server error, look for a small Node.js icon under the Next.js version badge. Clicking it copies the DevTools address for the server process, which you can paste into a new tab.
If you need the server to pause on the very first line (for example, to debug next.config.mjs), pass the flag through NODE_OPTIONS instead:
# macOS and Linux
NODE_OPTIONS=--inspect-brk npx next dev
On Windows, set the variable first in PowerShell with $env:NODE_OPTIONS="--inspect-brk", then run npx next dev.
Step 4: debug both sides in VS Code
Switching between terminal, browser and DevTools windows gets tiring. VS Code can do it all in one place with a launch configuration: a small JSON file that tells VS Code how to start and attach its debugger.
Create .vscode/launch.json in the project root (the .vscode folder sits next to package.json):
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"type": "node-terminal",
"request": "launch",
"command": "npm run dev -- --inspect"
},
{
"name": "Next.js: debug client-side",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000"
},
{
"name": "Next.js: debug full stack",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/next/dist/bin/next",
"runtimeArgs": ["--inspect"],
"skipFiles": ["<node_internals>/**"],
"serverReadyAction": {
"action": "debugWithEdge",
"killOnServerStop": true,
"pattern": "- Local:.+(https?://.+)",
"uriFormat": "%s",
"webRoot": "${workspaceFolder}"
}
}
]
}
What each one does:
- debug server-side starts
next dev --inspectin VS Code’s terminal and attaches automatically. Breakpoints in Server Components, Server Actions, Route Handlers andlib/files work. - debug client-side opens Chrome at your app (start
npm run devyourself first). Breakpoints in Client Components work. - debug full stack starts the server, waits until it prints its
- Local:address, then opens Edge and debugs both sides. If you use Chrome, changedebugWithEdgetodebugWithChrome.
To use them, open the Run and Debug panel (Ctrl + Shift + D, or Cmd + Shift + D on macOS), pick a configuration from the dropdown, and press F5. Now you can click in the gutter to the left of any line number to set a red breakpoint dot, without writing debugger at all.
Run and Debug panel
▶ Next.js: debug full stack ▼
VARIABLES
Local
id: "dune"
book: {id: 'dune', title: 'Dune', author: 'Frank Herbert', ...}
If you changed the port (for example with next dev -p 4000), change 3000 in the URLs too.
Step 5: read server errors in the terminal
Many bugs do not need a debugger at all. Next.js prints helpful errors in the terminal, often with a code frame that points to the exact line. Here is a real one from Book Nook, shown while the dev server was running:
Error: Route "/books/[id]": Next.js encountered URL data during prerendering or a navigation.
`params` or `searchParams` accessed outside of `<Suspense>` may prevent the navigation from being instant, leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with `<Suspense fallback={...}>` around the data access
- [block] Set `export const instant = false` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/instant-shell-url-data
at BookPage (app\books\[id]\page.js:26:18)
> 26 | const { id } = await params;
| ^
Read it from top to bottom: what went wrong (the route reads params outside a Suspense boundary), how to fix it (the bullet list), and where (the file, line 26). Here the fix is to add app/books/[id]/loading.js, as you learned in Loading UI, Suspense and Streaming.
If a production build fails while prerendering and the stack trace only points at body or html, rerun the build with extra detail:
npx next build --debug-prerender
This turns off server minification and adds source maps, so the trace shows your own file and line. Never deploy a build made with this flag. You will read build output properly in Building for Production.
A simple debugging routine
When something is wrong, walk through these questions in order:
- Server or client? Is the broken code in a file with
'use client', or in a Server Component, Server Action, orlib/helper? - Is there an error already? Check the error overlay, the terminal and the browser console before adding anything.
- What are the values? Add a
console.log(or a breakpoint) just before the line that misbehaves. - What did the network do? In the Network tab, check the status code and response of the request that should have changed things.
- Is it cached? With Cache Components, a “stale” value might simply be cached. Check whether a
'use cache'function needsupdateTagafter a change (see Cache Lifetimes, Tags and Revalidation).
Common mistakes
Looking for server logs in the browser
You add console.log to a Server Component, open the browser console, and it is not there (or it only shows in development). Server code logs to the terminal where npm run dev or npm start runs. On a host like Vercel, look in the deployment’s logs page.
Forgetting the extra --
npm run dev --inspect
Without the --, npm keeps --inspect for itself and Next.js never sees it, so no Debugger listening on line appears. Use npm run dev -- --inspect.
Expecting a client breakpoint to stop server code
A breakpoint set in the browser’s Sources panel only stops code that runs in the browser. To stop a Server Component, Server Action or Route Handler, use --inspect with chrome://inspect, or the VS Code server-side configuration.
Leaving debugger statements behind
A forgotten debugger pauses the page for anyone who has DevTools open. Search for debugger before you commit. ESLint’s no-debugger rule can warn you about them.
Quick recap
- A Next.js app is two programs: server code (terminal logs) and client code (browser console).
- Use browser DevTools and the
debuggerstatement for Client Components, and React Developer Tools to inspect props and state. - Start the dev server with
npm run dev -- --inspectand openchrome://inspectto pause server code. - A
.vscode/launch.jsonfile lets VS Code debug the server, the client, or both with F5. - Read terminal errors top to bottom: what, how to fix, and where.
- Use
next build --debug-prerenderfor clearer build stack traces, but never deploy that build.
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"type": "node-terminal",
"request": "launch",
"command": "npm run dev -- --inspect"
},
{
"name": "Next.js: debug client-side",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000"
},
{
"name": "Next.js: debug full stack",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/next/dist/bin/next",
"runtimeArgs": ["--inspect"],
"skipFiles": ["<node_internals>/**"],
"serverReadyAction": {
"action": "debugWithEdge",
"killOnServerStop": true,
"pattern": "- Local:.+(https?://.+)",
"uriFormat": "%s",
"webRoot": "${workspaceFolder}"
}
}
]
}
Was this explanation helpful?
Your feedback helps us improve the content for everyone.
Thank you for your feedback!
We appreciate you helping us improve.
What could we improve? (Optional)
Read aloud
Ready