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.

Related essays

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