Skip to content
All essays
WebJanuary 25, 202410 min

Frontend Debugging Tools and Techniques

Master frontend debugging with Chrome DevTools, React DevTools, and advanced debugging strategies

Ü
Ümit Uz
Mobile & Full Stack Developer

Debugging is half of development. Master these tools and techniques to squash bugs faster.

Chrome DevTools

Elements Panel

Inspect and modify DOM in real-time:

javascript
// Select elements document.querySelector('.button') document.querySelectorAll('div.item') // Computed styles getComputedStyle(element) // Force state // Right-click element > Force State > :hover

Console Panel

Logging strategies:

javascript
// Basic logging console.log('Variable:', variable); // Grouped logging console.group('User Data'); console.log('Name:', user.name); console.log('Email:', user.email); console.groupEnd(); // Table for objects console.table(users); // Timing operations console.time('Fetch data'); fetch('/api/data') .then(data => { console.timeEnd('Fetch data'); }); // Assertion console.assert(condition, 'Error message'); // Tracing console.trace('Function call stack'); // Counting console.count('ButtonClick');

Console filters:

  • -url:node_modules: Hide node_modules
  • file:app.js: Show only app.js

Network Panel

Monitor all network activity:

javascript
// Filter requests // - XHR/fetch // - JS, CSS, Img // - All // Check status codes // 200: OK // 301: Redirect // 404: Not found // 500: Server error // Inspect response // Headers, Payload, Preview, Response

Sources Panel

Set breakpoints and debug:

javascript
// Add breakpoint // Click line number in Sources panel // Conditional breakpoint // Right-click > Edit breakpoint // condition: x > 100 // Logpoint (no pause) // console.log(variable) // Debugging keywords debugger; // Pauses execution

React DevTools

Component Inspection

bash
npm install --save-dev react-devtools

Features:

  • View component hierarchy
  • Inspect props and state
  • Measure component performance
  • Trace component updates

Profiler Component

javascript
import { Profiler } from 'react'; function onRenderCallback( id, phase, actualDuration, baseDuration, startTime, commitTime ) { console.log({ id, phase, // 'mount' or 'update' actualDuration, // Time spent rendering }); } <Profiler id="App" onRender={onRenderCallback}> <App /> </Profiler>

Redux DevTools

State Inspection

javascript
import { configureStore } from '@reduxjs/toolkit'; const store = configureStore({ reducer: rootReducer, devTools: process.env.NODE_ENV !== 'production' });

Features:

  • Time-travel debugging
  • State inspection
  • Action history
  • State diffing

Performance Monitoring

Performance API

javascript
// Measure performance performance.mark('start'); // Your code fetchData().then(() => { performance.mark('end'); performance.measure('fetchData', 'start', 'end'); const measure = performance.getEntriesByName('fetchData')[0]; console.log(`Took ${measure.duration}ms`); });

User Timing API

javascript
// Mark important moments performance.mark('component-mount'); performance.mark('data-loaded'); // Measure between marks performance.measure( 'data-loading-time', 'component-mount', 'data-loaded' ); // Get measures const measures = performance.getEntriesByType('measure'); console.table(measures);

Performance Observer

javascript
// Observe long tasks const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { console.log('Long task detected:', entry); } }); observer.observe({ entryTypes: ['longtask'] });

Memory Debugging

Memory Leaks

Common causes:

  1. 1Unclosed intervals/timeouts
  2. 2Event listeners not removed
  3. 3Closures retaining references
  4. 4DOM references in objects

Detection:

javascript
// Take memory snapshot // DevTools > Memory > Take Snapshot // Look for: // - Detached DOM nodes // - Event listeners // - Global variables

Heap Snapshots

javascript
// Force garbage collection // (in Chrome, with --js-flags flag) // Compare snapshots // Snapshot 1 > Use app > Snapshot 2 // Compare to find leaks

Network Debugging

Fetch API

javascript
fetch('/api/data') .then(response => { // Log response console.log('Status:', response.status); console.log('Headers:', response.headers); return response.json(); }) .then(data => { console.log('Data:', data); }) .catch(error => { console.error('Error:', error); });

Request Interception

javascript
// Service Worker for debugging self.addEventListener('fetch', (event) => { console.log('Fetch:', event.request.url); event.respondWith( fetch(event.request).then(response => { console.log('Response:', response.status); return response; }) ); });

Error Tracking

Try-Catch

javascript
try { riskyOperation(); } catch (error) { console.error('Caught error:', error); // Log to error tracking service logError(error); }

Global Error Handler

javascript
// Catch unhandled errors window.addEventListener('error', (event) => { console.error('Global error:', event.error); logError(event.error); }); // Catch unhandled promise rejections window.addEventListener('unhandledrejection', (event) => { console.error('Unhandled rejection:', event.reason); event.preventDefault(); // Prevent default handling });

Error Boundaries (React)

javascript
class ErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, info) { console.error('Error boundary caught:', error, info); logError(error, info); } render() { if (this.state.hasError) { return <h1>Something went wrong.</h1>; } return this.props.children; } }

Source Maps

Configuration

javascript
// vite.config.js export default defineConfig({ build: { sourcemap: true } });

Benefits:

  • Debug minified code
  • See original file names
  • Set breakpoints in source
  • Console logs show original location

Mobile Debugging

Remote Debugging

Android:

  1. 1Enable USB debugging
  2. 2Connect to Chrome
  3. 3chrome://inspect

iOS:

  1. 1Safari > Develop > [Device]
  2. 2Inspect web views

Eruda (Mobile Console)

html
<script src="//cdn.jsdelivr.net/npm/eruda"></script> <script>eruda.init();</script>

Debugging Tips

1. Reproduce the Bug

Consistently reproduce the issue before fixing.

2. Isolate the Problem

Comment out code, use binary search.

3. Read Error Messages

Errors often tell you exactly what's wrong.

4. Use Debugger

Step through code line by line.

5. Check Assumptions

Verify your assumptions about the code.

6. Check Recent Changes

Bugs often appear after recent modifications.

7. Take Breaks

Fresh eyes spot bugs faster.

Common Debugging Scenarios

State Not Updating

javascript
// Bad: Direct mutation state.items.push(item); // Won't trigger re-render // Good: Immutable update setState({ ...state, items: [...state.items, item] });

Async Issues

javascript
// Bad: Race condition useEffect(() => { fetchData(userId); }, [userId]); // Good: Cleanup useEffect(() => { let cancelled = false; fetchData(userId).then(data => { if (!cancelled) { setState(data); } }); return () => { cancelled = true; }; }, [userId]);

Memory Leaks

javascript
// Bad: Not cleaning up useEffect(() => { const interval = setInterval(() => { updateData(); }, 1000); // Missing cleanup! }, []); // Good: Cleanup function useEffect(() => { const interval = setInterval(() => { updateData(); }, 1000); return () => { clearInterval(interval); }; }, []);

Conclusion

Master debugging tools and you'll fix bugs 10x faster. Learn Chrome DevTools deeply, use React DevTools for component debugging, and always add proper error handling. Debugging is a skill—practice makes perfect.

Remember: The best debugging is prevention. Write clear code, add tests, and use TypeScript to catch errors before runtime.

Next essay
Modern CLI Frameworks: Oclif, Yargs, and Cac