JavaScript Debugging: A Practical Workflow for 2026

Debug JavaScript by reproducing the failure and reading the original stack trace in the console, not the framework overlay. For production-only bugs, upload source maps to an error tracker so traces point to real files. Then set breakpoints and step through the async chain instead of guessing.
Part of the complete web development guide.
On this page
- Start by getting a real error, not a guess
- Breakpoints beat console.log, but only when you use them right
- Async bugs are almost always about the stack you lost
- When it’s not your code
- Build in a way that makes debugging easier
- FAQ
- Is console.log still fine in 2026?
- How do I debug minified production code?
- What’s the fastest way to find a bug that only happens in production?
Most JavaScript debugging advice is either “use console.log” or a tour of a browser panel you already know about. Neither helps when you’re staring at a bug that only shows up in production, or an async chain that swallowed the actual error three layers down. Here’s the workflow I actually use, updated for how we build things in 2026.

Start by getting a real error, not a guess
Half of debugging is refusing to fix the wrong thing. Before you touch code, make the failure reproducible and read the actual stack trace instead of the summary your framework prints. React, Next, and most modern meta-frameworks show you a friendly error overlay that hides the useful part. Open the console and find the original throw.
If the error only happens in production, stop guessing and get real data. Client-side error tracking with source maps uploaded at build time is the difference between a minified stack trace that means nothing and one that points to checkout.ts:112. Sentry, Bugsnag, and similar tools all do this, and it’s worth the setup cost.
// Upload source maps during build so production traces are readable
// Next.js example
module.exports = {
productionBrowserSourceMaps: true,
// then let your error tool's build plugin upload them
};
One caveat: don’t ship source maps publicly unless you want your source readable by anyone. Upload them to your error service and keep them off the public CDN.

Breakpoints beat console.log, but only when you use them right
I still use console.log constantly. It’s fast and it works. The problem is when you’re logging inside a loop or a hot path and you can’t tell which call produced which line. That’s when a debugger earns its place.
In Chrome DevTools and Firefox, the things that pay off most:
- Conditional breakpoints. Right-click a line number and add a condition like
user.id === 42. It only pauses when it matters. - Logpoints. Instead of a full pause, a logpoint prints a message to the console without editing your code. Great for code you don’t want to touch, like something inside
node_modules. - The debugger statement. Drop
debugger;in your source and it pauses exactly there, in whatever environment DevTools is attached to. Works in Node too.
function processOrder(order) {
// only pause when this specific customer is involved
debugger; // add a conditional breakpoint on this line instead if it's noisy
return charge(order);
}
For Node, run with node --inspect-brk ./server.js and open chrome://inspect. You get the same debugger UI against your backend, which is far better than adding print statements and restarting.
Async bugs are almost always about the stack you lost
The classic 2026 async problem: an error gets thrown inside a promise, gets caught somewhere it shouldn’t, and the stack trace points at framework internals instead of your code. Two things fix most of this.
First, use async/await and let errors propagate. A bare .catch(() => {}) is where bugs go to hide. Second, enable async stack traces. DevTools has had them on by default for years, but Node needs you to be aware of how you’re running it:
// Bad: swallows the real error
fetchData().catch(() => {});
// Better: let it surface, or handle it with context
try {
const data = await fetchData();
} catch (err) {
console.error('fetchData failed', err);
throw err; // don't hide it from the caller
}
If you’re debugging a chain and the stack makes no sense, check whether something is catching and re-throwing a new error without the original. new Error('failed') throws away the cause. Use the cause option instead:
try {
await doWork();
} catch (err) {
throw new Error('doWork failed', { cause: err });
}
Now the original error shows up in the stack and in most error trackers.
When it’s not your code
Some bugs live in the interaction between your code and everything around it: a library version, a browser quirk, a caching layer. A few habits that save hours:
- Reproduce in a clean environment. Incognito, no extensions, fresh cache. Half of “it works on my machine” is a stale build or a rogue extension.
- Bisect your dependencies. If a bug appeared after an update,
git bisector just pinning versions back one at a time finds it fast. - Check the network tab before the console. A surprising number of “JavaScript bugs” are a 404, a CORS failure, or a response that came back in a shape you didn’t expect.
I do a lot of this on client work, and the pattern holds whether it’s a small marketing site or a full app. If you’re maintaining something that’s gotten hard to debug because it was never built to be maintained, that’s usually a sign the foundation needs work, not just the bug. That’s the kind of thing custom web development should account for from the start.
Build in a way that makes debugging easier
You can’t debug your way out of code that was never structured to be understood. A few things that pay off long before the bug shows up:
- Keep functions small and single-purpose. A 300-line function is a 300-line crime scene.
- Name things honestly.
dataandtemptell you nothing at 11pm. - Fail loudly in development and gracefully in production. Don’t let both look the same.
- Write the occasional test around the bug you just fixed. It’s the cheapest regression insurance there is.
None of this is glamorous. Debugging never is. But a repeatable workflow beats heroics every time, and the developers who ship reliably are usually just the ones who got good at finding the actual problem before trying to fix it.
FAQ
Is console.log still fine in 2026?
Yes, for quick checks. Switch to breakpoints and logpoints when the output gets noisy or you need to inspect state at a specific moment.
How do I debug minified production code?
Upload source maps to your error tracking service at build time and keep them off the public CDN. Then production stack traces point back to your real source.
What’s the fastest way to find a bug that only happens in production?
Get real error data with stack traces and context first. Guessing at a bug you can’t reproduce usually wastes more time than setting up proper error tracking once.


