TL;DR: Chrome DevTools (press F12 or Cmd+Option+I on Mac) is a full debugging environment built into your browser. The Console tab shows JavaScript errors with exact file names and line numbers. The Network tab shows every API call, its request, its response, and its status code. The Elements tab shows rendered HTML and lets you edit CSS live. The Application tab shows localStorage, cookies, and session data. The Sources tab lets you set breakpoints and pause JavaScript mid-execution. Together, these five tabs solve 95% of the bugs Chuck runs into — and most of the time, opening just one of them tells you exactly what's wrong in under a minute.

Why AI Coders Need This

Here is what most vibe coders do when something breaks: they paste the broken code into Claude or Cursor and ask it to fix the problem. Sometimes this works. Often it doesn't — because the AI is guessing based on the code, not based on what is actually happening at runtime.

Chrome DevTools watches your app run in real time. It sees the actual error message, the exact line that crashed, the precise HTTP status code your server returned, the real data that came back from your API, and the specific CSS rule that's making your layout look wrong. This is information the AI does not have unless you give it to them. DevTools gives it to you instantly.

The pattern that actually works: open DevTools first, read what it says, then paste that specific information into your AI chat. "My component is broken" gets you a guess. "Console says: TypeError: Cannot read properties of undefined (reading 'map') at ProductList.jsx:47" gets you the fix.

See also: Browser DevTools Guide for a broader overview of DevTools across Chrome, Firefox, and Safari.

Opening DevTools

Three ways to open it — use whichever becomes muscle memory:

MethodWindows / LinuxMac
Keyboard shortcutF12 or Ctrl+Shift+ICmd+Option+I
Right-click menuRight-click anything → Inspect
Chrome menuThree-dot menu → More Tools → Developer Tools

DevTools opens docked to the bottom or side of the browser. If it feels cramped, click the three-dot menu inside DevTools and choose Undock into separate window — then you can put it on a second monitor and give your app full screen.

The tabs across the top are: Elements, Console, Sources, Network, Performance, Memory, Application, Security, Lighthouse. You will mostly live in five of them: Console, Network, Elements, Application, and Sources. That is what this guide covers.

Build the habit

Open DevTools before you test your app. Not after it breaks. Have the Console tab visible while you work. Errors appear the moment they happen — you will catch them immediately instead of wondering why something silently stopped working.

The Console Tab: Reading Errors Like a Human

The Console tab is the first place to look when something is broken. It shows three things: JavaScript errors (red), warnings (yellow), and anything your code logs with console.log().

Anatomy of a console error

Every error gives you the same information in the same format:

Uncaught TypeError: Cannot read properties of undefined (reading 'map')
    at ProductList (ProductList.jsx:47:18)
    at renderWithHooks (react-dom.development.js:14985:18)

Line by line:

  • Error type: TypeError — tried to use something as if it existed, but it was undefined
  • What failed: Cannot read properties of undefined (reading 'map') — something called .map() on undefined
  • Where it happened: ProductList.jsx:47:18 — your file, line 47, column 18. Click this link and DevTools jumps straight there.
  • Stack trace: the chain of function calls that led to the crash — read top to bottom, your code is at the top

Most of the time, the fix is obvious once you read the error. Cannot read properties of undefined means data hasn't loaded yet — you need a loading state or a conditional check before you try to use the data.

Common error types and what they mean

  • TypeError: You used something the wrong way — called a function on something that isn't a function, or accessed a property on undefined or null
  • ReferenceError: You used a variable name that doesn't exist — typo, out of scope, or imported from the wrong place
  • SyntaxError: JavaScript can't parse your code — usually a missing bracket, comma, or quote
  • NetworkError / Failed to fetch: An API call failed — check the Network tab for details
  • CORS error: Your API is blocking requests from your frontend — see What Is CORS?

Running JavaScript in the Console

The Console is a live JavaScript playground. Click it and type anything — it runs against the current page:

// Check if your auth token is there
localStorage.getItem('authToken')

// See what's in a variable (if it's on window)
window.__APP_DATA__

// Test a quick function
JSON.parse('{"userId": 42}')

// Find elements on the page
document.querySelectorAll('.product-card').length

// Force a state for testing
document.title = 'Testing'

This is how you verify your assumptions without changing code and reloading. Before asking AI "why is my auth token missing?", check localStorage.getItem('authToken') right here. Either it's there or it isn't — now you know where the problem actually is.

Filtering the noise

When an app has a lot going on, the Console fills up fast. Use the filter bar at the top to search for specific text. Use the log level buttons (Errors, Warnings, Info) to hide everything except what you care about. When debugging, showing Errors only is usually the right move — it strips out all the console.log noise AI-generated code tends to leave behind.

The Network Tab: Why Your API Call Failed

The Network tab records every HTTP request your page makes. Every API call, every image load, every JavaScript file, every stylesheet. For debugging apps that talk to a backend or third-party service, this is your most important tab.

How to use it

  1. Open DevTools → click Network
  2. Reload the page, or trigger the action that makes the request (click a button, submit a form)
  3. Click Fetch/XHR in the filter bar to show only API calls — this hides all the static file noise
  4. Click any request to open its detail panel

What each sub-tab tells you

  • Headers: The request method (GET, POST), the full URL, the request headers (including your Authorization token if you're sending one), and the response headers (including CORS headers)
  • Payload: The request body — what your app sent to the server. For POST requests, this is where you verify the data you thought you were sending
  • Response: The raw response from the server — the actual JSON (or HTML, or error message)
  • Preview: The response formatted so it's readable — JSON is expandable, great for quickly inspecting nested objects
  • Timing: How long each phase took — useful if your app is slow but not broken

Reading status codes

The status code column tells you immediately what kind of failure you have. Failed requests show in red:

StatusWhat it meansWhere to look
200Success — request workedCheck the Response tab if data looks wrong
201Created — POST workedAll good
400Bad request — you sent wrong dataCheck Payload; check Response for the error message
401Unauthorized — missing or bad auth tokenCheck Headers → is Authorization present?
403Forbidden — auth works, but no permissionCheck your user's role/permissions
404Not found — URL is wrongCheck the request URL in Headers
429Rate limited — too many requestsSlow down your calls or check for loops
500Server error — backend crashedCheck your server logs
(failed)Network error — no connectionIs your dev server running?
CORS errorBlocked by browser securityConsole tab → see CORS guide

Understanding how HTTP requests and fetch work at a deeper level: see What Is the Fetch API?

Checking what you actually sent

One of the most common bugs: you think you're sending certain data to your API, but you're sending something different — or nothing at all. Click a POST or PUT request → Payload tab → read exactly what went over the wire. This catches the "AI generated the fetch call but the body structure doesn't match what the API expects" problem immediately.

The Elements Tab: Fixing CSS Without Guessing

The Elements tab shows the live HTML your browser has rendered — not the source HTML files, but the DOM as it exists right now, including anything JavaScript has added or changed. The Styles panel on the right shows every CSS rule affecting the selected element.

Inspecting a specific element

Right-click any element on the page and choose Inspect. The Elements panel jumps to that element in the HTML tree. The Styles panel shows all CSS rules applied to it, ordered by specificity (most specific at the top).

Rules with a strikethrough are being overridden by a more specific rule above them. This is how you find out why your color isn't applying, why your margin is wrong, or why your padding seems to be coming from somewhere you didn't set it.

Editing CSS live

Click any CSS value in the Styles panel and type to change it. Press Tab to move to the next property. Add a new property by clicking at the end of any rule. Everything updates instantly on the page — no reload, no save, no build process.

This is the correct workflow for CSS debugging:

  1. Right-click the broken element → Inspect
  2. Read the Styles panel until you understand why it looks wrong
  3. Edit CSS values live until it looks right
  4. Copy the values that work into your actual stylesheet

Never guess CSS by editing your file, saving, and reloading. That takes 10x longer and gives you no visibility into what other rules you're fighting. DevTools shows you everything at once.

The box model panel

In the Styles panel, scroll to the bottom to see the box model diagram — a visual representation of the element's content size, padding, border, and margin. When elements are overlapping, the wrong size, or not centered like they should be, looking at the box model tells you exactly which layer of spacing is wrong. See What Is CSS? for how the box model works conceptually.

Checking computed values

Click the Computed tab next to Styles to see the final, resolved value of every CSS property — after all overrides, inheritance, and browser defaults. This is useful when the Styles panel shows you a value but the element doesn't look that way — the Computed tab shows what the browser actually ended up applying.

The Application Tab: localStorage, Cookies, and Env

The Application tab is where you inspect persistent data — things your app stores between page loads. This is the tab you open when your app "forgets" a login, when saved settings aren't persisting, or when you want to verify that your auth token is actually being stored.

localStorage and sessionStorage

In the left sidebar, expand Storage → Local Storage → your domain. You see every key-value pair stored in localStorage as a simple table. Click any row to see the value. Double-click to edit it. Right-click → Delete to remove individual items. Click the clear icon to wipe everything.

Common debugging uses:

  • Verify your auth token is being saved after login
  • Check that user preferences are persisting correctly
  • Manually clear stale data to test the logged-out state
  • Inspect the exact format of stored data — sometimes what you think you stored and what's actually there are different

sessionStorage works the same way — different sidebar item, clears when the tab closes.

Cookies

Expand Cookies → your domain. You see every cookie — name, value, domain, path, expiration, and security flags (HttpOnly, Secure, SameSite). This is how you verify that your session cookie is being set, check its expiration, or diagnose why your cookie-based auth isn't working.

If a cookie has HttpOnly set, JavaScript can't read it — but DevTools can see it here. That is often the answer to "why can't my JS read the auth cookie?"

IndexedDB and Cache Storage

If your AI-generated app uses IndexedDB (common in more complex offline-capable apps) or the Cache API (service workers), they are visible here too. You can browse stored records and delete entries to test fresh states.

Environment variables (a note)

DevTools cannot show you server-side environment variables — those stay on the server and are never sent to the browser. But it can show you anything your app exposes to the client: variables injected into the page as globals, values baked into your JavaScript bundle at build time (like Vite's import.meta.env.VITE_* values), or anything stored in localStorage. If your API key is appearing in the browser at all, it's visible here.

The Sources Tab: Breakpoints

The Sources tab shows the JavaScript files your browser is running and lets you set breakpoints — markers that pause execution so you can inspect what's happening at a specific moment in time.

console.log debugging means you have to predict in advance what you want to inspect, add the log, save, reload, and check. A breakpoint pauses execution right where you need it and lets you inspect everything — every variable in scope, the call stack, the state of the whole app — without changing your code at all.

Setting a breakpoint

  1. Open the Sources tab → find your file in the left file tree (or use Ctrl+P / Cmd+P to search by filename)
  2. Click a line number — a blue marker appears
  3. Trigger the code that should run that line (click a button, submit a form, load the page)
  4. Execution pauses at the breakpoint — the line is highlighted
  5. Hover any variable to see its current value in a tooltip
  6. Check the Scope panel on the right for all local and global variables
  7. Use the resume/step buttons (or F8 to resume, F10 to step over, F11 to step into)

Conditional breakpoints

Right-click a line number → Add conditional breakpoint. Type a JavaScript expression — the breakpoint only fires when that expression is true. Useful when code runs in a loop and you only want to pause on a specific iteration: user.id === 42 or items.length === 0.

Logpoints (console.log without editing code)

Right-click a line number → Add logpoint. Type an expression. Every time that line runs, it logs the value to the Console — without modifying your source code at all. This is cleaner than adding and removing console.log statements constantly.

Common Debugging Scenarios: 5 Real Examples

Scenario 1: "My page is completely blank"

Open: Console tab

A blank page almost always means JavaScript crashed before it could render anything. Open the Console and you'll see the error — usually a SyntaxError or import error that broke the entire bundle, or a crash in the root component.

Look for red errors. Click the file link. Read the message. It will be one of: a file that can't be found (wrong import path), a syntax error from malformed code, or an environment variable that's undefined that the app tries to use at startup.

If the Console is empty and the page is blank, check the Network tab — the HTML file itself might be failing to load (wrong dev server URL, server not running).

Scenario 2: "My API call isn't working"

Open: Network tab

Click the Network tab, filter to Fetch/XHR, then trigger the action that makes the call. Look at what appears.

If nothing appears: the fetch call isn't running at all — the bug is in your JavaScript logic, not the API. Check Console for a JS error.

If it appears red: click it and check the status code. 401 means your auth token isn't there — check Headers → Authorization. 404 means the URL is wrong — check the request URL. 500 means your server crashed — check your server logs. CORS error means the browser blocked it — the Console has the specific header name that's missing.

If it's green (200) but the app is broken anyway: click Preview. The response data isn't what your app expects — the structure is different, there's an error message in the body, or the data is wrapped in a different key than the AI assumed.

Scenario 3: "My styles look completely wrong"

Open: Elements tab

Right-click the element that looks wrong → Inspect. Scan the Styles panel — is your rule present? Is it being overridden (strikethrough)? Is a value unexpected (like margin: 0 coming from a reset stylesheet)?

Check the Computed tab → is the actual computed value what you expected? Sometimes you set a value in CSS but a more specific rule elsewhere wins.

Edit values live in the Styles panel until the element looks right. The fix is usually: a specificity conflict you need to resolve, a missing unit (wrote 16 instead of 16px), or a parent container constraining the layout in a way you didn't realize. See What Is CSS? for how specificity works.

Scenario 4: "My data isn't saving / I keep getting logged out"

Open: Application tab

Go to Application → Local Storage → your domain. Is the auth token there? Is the data you thought you saved actually present?

If localStorage is empty when it should have data: the save call isn't running. Add a breakpoint in the Sources tab right before the localStorage.setItem() call and check whether it fires.

If the token is there but you're still getting logged out: check the token value — is it expired? Is it malformed? Copy the JWT value and decode it at jwt.io to check the expiry timestamp.

If you're using cookies for auth, check Application → Cookies. Is the cookie there? Check its SameSite, Secure, and expiration values — a SameSite=Strict cookie won't send on cross-origin requests.

Scenario 5: "My button does nothing when I click it"

Open: Console + Sources tabs

First, check the Console for any errors that appear when you click. Often there is a JS crash that silently swallows the click handler — the error tells you exactly what broke.

If there's no error: open Sources, find your click handler function, set a breakpoint on the first line. Click the button. Did execution pause? If yes — the handler is firing, the bug is inside it. Step through line by line and watch variables change. If no — the event listener isn't attached. Check that the element has the handler, the component mounted correctly, and the button isn't being covered by an invisible overlay element (check the Elements tab — sometimes a transparent div sits on top).

What AI Gets Wrong About Debugging

When you ask AI to fix a bug without showing it the actual error, it guesses. It's a smart, well-informed guess — but it's still a guess. Here's where that goes wrong:

AI assumes the error is in the code you showed it. But the real problem might be in the data your API returned, in an environment variable that's missing, in a CSS override from a library stylesheet, or in something that only exists at runtime. DevTools sees runtime. AI sees code.

AI often adds defensive code instead of fixing the root cause. Tell AI "my component crashes" and it will often add if (data?.items) guards everywhere to prevent the crash — without finding out why data.items is undefined in the first place. The Network tab would have shown that the API returned { "results": [...] } not { "items": [...] } — a one-word fix, not a defensive refactor.

AI doesn't know your specific request failed. It can't see that your fetch is returning a 401 because your Supabase anon key is wrong. It can't see that CORS is blocking your call because you forgot to add your localhost URL to the allowed origins. You can see all of this in the Network tab in five seconds.

The right pattern: DevTools first, AI second. Get the real error from DevTools, paste it to AI with context, get a targeted fix. See also What Is Error Handling? to understand how errors propagate in JS apps.

The copy-paste trap

When you get a red error in the Console, copy the full error message — including the file name and line number — before pasting to AI. "My code is broken" gets generic advice. "Uncaught TypeError: Cannot read properties of undefined (reading 'map') at ProductList.jsx:47" gets a precise, correct fix almost every time.

FAQ

Press F12 on Windows/Linux, or Cmd+Option+I on Mac. Right-clicking anything on the page and choosing Inspect opens DevTools with the Elements panel already showing that element. Make opening DevTools a reflex — do it every time you run your app, not just when something breaks.

Open Network tab → filter to Fetch/XHR → click the failed request → Headers tab. Look at the Request Headers section — is there an Authorization header? If yes, check that the value looks right (should be Bearer eyJ... for JWT, not undefined or empty). If there's no Authorization header at all, your code isn't attaching the token — check where you read the token from localStorage and how you pass it to the fetch call.

Click the Errors button in the Console filter bar — it hides everything except actual errors. You can also type a search term in the filter box to show only matching messages. If your codebase has a lot of console.log noise from AI-generated debug statements, ask AI to remove all console.log calls that aren't errors when you're done building a feature.

Yes. Click the device toggle icon (looks like a phone and tablet) in the DevTools toolbar — it is in the top-left corner next to the element picker. This activates Device Mode, where you can simulate specific devices (iPhone 14, Pixel 7, etc.), set custom viewport sizes, throttle the network to simulate slow connections, and simulate touch events. Essential for testing responsive layouts.

No. CSS edits in the Elements tab, JavaScript changes in the Sources tab, and data edits in the Application tab are all temporary — they disappear when you reload. DevTools is for experimenting and diagnosing, not for making permanent changes. Once you find the fix in DevTools, copy it to your actual source files and save.

What to Learn Next

Your first move

Open your current project in Chrome. Press Cmd+Option+I (Mac) or F12 (Windows). Click the Console tab. Are there any errors? If yes — click the file link in one of them. That is exactly where your next bug to fix is hiding.