Why modern JS debugging matters
Applications now span browsers, Node servers, and build/transpile pipelines. Good debugging practice speeds diagnosis, reduces trial-and-error, and keeps production safe. This guide focuses on actionable techniques you can use immediately with Chrome DevTools, Node's inspector, and VS Code.
Quick checklist before you start
- Can you reproduce the bug locally? If not, capture a minimal reproducible case (HTTP trace, input payload, steps).
- Are source maps enabled for transpiled code (TypeScript/Babel)?
- Do you have structured logs/trace IDs to correlate client and server events?
Core techniques (fast to apply)
1) Console and structured logging
Start small: log the minimal state you need. Prefer structured JSON logs so machines and log tools can parse them.
const event = { event: 'order_created', orderId: id, amount: total };
console.log(JSON.stringify(event));
// For humans: console.dir(order, { depth: 2 });2) Inline debugger
Insert debugger; to pause execution where you need to inspect variables in DevTools or an attached debugger.
function calculate(a, b) {
// open DevTools and the debugger will pause here
debugger;
return a + b;
}3) Conditional breakpoints
When a loop iterates many times or a function is called repeatedly, use a conditional breakpoint expression instead of many logs:
// Put a normal breakpoint and set condition: args[0] === 'special'
function handleEvent(...args) {
// breakpoint condition is faster than logging every call
doWork(args);
}Debugging Node.js
Start Node with the inspector
Run Node with the inspector and optionally break on the first line. Then attach DevTools or a debugger client.
// start Node with inspector enabled
// $ node --inspect-brk=0.0.0.0:9229 ./app.js
// then open chrome://inspect in Chrome or attach a debugger clientAttach from VS Code
Create a lightweight attach configuration and use it to inspect breakpoints and call stacks.
{
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "attach",
"name": "Attach to Node",
"port": 9229,
"restart": true,
"localRoot": "${workspaceFolder}",
"remoteRoot": "."
}
]
}Debugging asynchronous code
Preserve and inspect async stacks
Async stack traces can be fragmented. Capture and log errors early, and print the stack to see the origin chain.
async function fetchData() {
try {
const res = await fetch('/api/data');
return await res.json();
} catch (err) {
// log the error and stack immediately
console.error('fetchData failed', err);
console.error(err.stack);
throw err;
}
}Use DevTools async call stack features
Chrome DevTools has options to show asynchronous call stacks; enable them in the DevTools settings to get clearer traces across awaits and timers.
Source maps and transpiled code
If your app uses TypeScript or Babel, enable source maps so the debugger and DevTools map runtime code back to original sources.
{
"compilerOptions": {
"sourceMap": true,
"inlineSources": true
}
}With source maps enabled you can set breakpoints in TypeScript, not the compiled JavaScript.
When to use logs vs breakpoints
- Logs are better for long-running systems, postmortem analysis, and correlating across services.
- Breakpoints and the inspector are best for interactive exploration, inspecting the heap, and stepping through code paths.
Practical recipes
-
Hard-to-reproduce production bug:
- Add structured debug logs around the suspected code path with a short-lived feature flag.
- Capture payload, trace ID, and environment metadata.
- Reproduce locally using the captured payload; switch to debugger once reproduced.
-
Slow API handler in Node:
- Start Node with --inspect and attach a profiler from DevTools.
- Take a CPU profile while reproducing the slow endpoint and inspect heavy functions.
-
Transpiled frontend bug (minified build):
- Ensure source maps are uploaded to your error tracking (Sentry or similar) and enable mapping.
- Set breakpoints in original TypeScript/JSX files in DevTools once source maps are in place.
Common pitfalls and tradeoffs
- Leaving verbose logs or debugger statements in production can leak secrets and harm performance—use short-lived flags or sampling.
- Source maps make debugging easier but increase build artifact size and require secure handling for production sourcemap uploads.
- Conditional breakpoints are powerful but can slow execution in tight loops if the condition is expensive.
Extra tips
- Use console.time / console.timeEnd for quick timing checks.
- Use a structured logger (pino, bunyan, winston) in Node for log level control and JSON output.
- For complex async debugging, record traces (OpenTelemetry) to see cross-service flows.
Conclusion
Start with reproducing the bug, prefer structured logging to gather evidence, and switch to the inspector or VS Code for interactive diagnosis. Source maps and async-stack awareness make modern debugging far more effective. Use conditional breakpoints and profiling only when needed, and always remove debug-only artifacts before long-term production runs.
Quick actionable next steps: enable source maps in your build, try node --inspect on a local run, and add one structured log around your failing code path.
Further reading: Debugging JavaScript: The Guide I Wish I Had Earlier (2026)
Was this helpful?
Share this post
Comments (0)
Want to join the conversation?
Log in or sign up to leave a comment and share your thoughts.
Log in to Comment