DevTools: the four panels that earn their keep
A working tour of the browser's Chromium DevTools, the panels you use to inspect the DOM, network requests, console state, and storage on every web app you build.
The last two lessons mapped the pipeline. Lesson 1 named the four network stages from URL commit to first byte, and lesson 2 named the six browser stages from first byte to pixels. Both lessons used the Network panel in passing, mostly the protocol column and the timing breakdown. DevTools is much bigger than that one tab, though, and you’ll be working inside the rest of it for the entire course.
DevTools is how you read the result of every change you make as a web developer. You change a class, reload, and look. You click a button and see what request went out. You suspect a stale cookie and read the cookie jar. This lesson is a tour of the four panels that earn a place on a SaaS engineer’s daily path, each answering one question you’ll ask constantly.
- Elements: what’s actually rendered right now?
- Network: what did the server send back?
- Console: what does the page think it knows?
- Application: what’s in storage right now?
Two more panels, Performance and Lighthouse, get a lot of attention, but we’ll defer them. You can’t tune performance on an empty teaching page, so that work lands later in the course when there’s a real app to measure. The same goes for a handful of other panels. We’ll name them once near the end so you know where they live, then move on.
The goal of this lesson isn’t mastery of any one panel. It’s the habit of reaching for the right one without thinking: you skim a bug report and know which panel to open, and you know a few moves in each panel that most newcomers never discover. This lesson gives you the layout; the depth in any one panel comes from using it on real work.
Open any well-styled site in a new browser tab now, with DevTools docked to the side or bottom; react.dev works well. We’ll switch between panels as the lesson goes.
Setting up: Chromium and React DevTools
Section titled “Setting up: Chromium and React DevTools”The course teaches against Chromium DevTools. Any Chromium browser, including Chrome, Edge, Arc, and Brave, ships the same DevTools, and the React DevTools extension installs cleanly in all of them. Firefox DevTools is a good cross-browser sanity check: the layout differs but the concepts transfer. Safari DevTools is what you’ll reach for in the iOS-specific work the deployment unit covers later in the course. For everything else, Chromium is the default.
You’ll need one extension installed before the React unit ships its first component: React DevTools. Installing it now means it’s ready for the rest of the course. The extension adds two tabs to DevTools, Components and Profiler, that read the React tree directly rather than the DOM. We won’t use them in this lesson, but we’ll need them later.
-
Open the Chrome Web Store listing for React Developer Tools and click Add to Chrome. The same listing works for any Chromium-based browser.
-
Open a page that uses React, such as
react.dev, with DevTools open. Two new tabs labelled Components and Profiler appear in the DevTools tab strip. That confirms the install. -
If you’re on Firefox, install the same extension from Firefox Add-ons. Same UI, same two tabs.
The mechanics of those two tabs land later in the course: the render-tree drill arrives with the React unit, and the Profiler returns in the performance unit. Install it once and ignore it for now.
Elements: what’s actually rendered?
Section titled “Elements: what’s actually rendered?”The Elements panel shows the live DOM : the in-memory tree as it stands at this exact moment, after every change JavaScript has made to it. That distinction matters. The source HTML the server originally sent is a different thing, which you read with View Source (Ctrl/Cmd+U), and on a React app the two routinely disagree. That disagreement is how React is supposed to work, not a bug.
The right pane of the Elements panel shows the cascade for whatever element you’ve selected: every CSS rule that targeted it, listed in cascade order, with overridden rules struck through. This is how an experienced engineer reads the cascade in practice. How the cascade itself works, including specificity, layers, and why one rule wins, gets its own chapter later in the course when Tailwind lands. Here you’re only learning to read it.
:hov button — force pseudo-states without using the mouse.
Five moves carry the panel’s daily value:
-
Inspect to find.
Cmd+Shift+Con macOS,Ctrl+Shift+Celsewhere, toggles the inspector cursor. Click any pixel on the page and the corresponding DOM node lights up in the tree. This is what you do when a user says “this thing isn’t styled right”: you start at the pixel, not at the source file. -
Read the cascade in the Styles pane. Rules are listed top-down by which one won, with overridden rules struck through. The struck-through line tells you why your rule didn’t apply, usually because of specificity or order. When the Tailwind chapter teaches layer ordering later in the course, this panel is where you’ll read it.
-
Edit styles live, with no reload. Click any rule’s value to edit it in place, and the page updates as you type. A reliable rule of thumb: if it works in DevTools, it’ll work in the code. Make the change here first, confirm it does what you want, then go write it. This saves a surprising amount of time over a career.
-
Toggle pseudo-states explicitly. Click
:hovin the Styles pane to force:hover,:focus-visible,:active, or:focus-withinon the selected element. The obvious instinct is to hover the element directly to see its hover style, but moving the mouse over to the panel drops the hover state. Forcing the toggle is how you hold the state still. -
Computed tab for the final value. When the cascade gets dense and you stop caring why a value won, the Computed tab shows the single resolved value the browser ended up using, plus which selector won it. Use this when the question is “what is the color, regardless of where it came from.”
Try it now. Open the page in front of you, inspect any button, and click :hov in the Styles pane to toggle :hover. The styles change without your mouse touching the button. Then edit the background-color value in the Styles pane directly: set it to red, or anything else. The button updates with no reload. That is the edit-and-check loop tightened from minutes to milliseconds.
Network: what did the server send back?
Section titled “Network: what did the server send back?”The Network panel logs every request the page makes, including Document, Fetch/XHR, JS, CSS, Images, Fonts, and WebSockets, and shows each as a row with timing, headers, payload, and response. Lesson 1 of this chapter used the Protocol column and the Timing breakdown, which were just a slice of this one panel. The rest of it, including the toolbar checkboxes, the throttle, and the request-detail tabs, is where you’ll spend real time when you debug a SaaS app.
Seven moves separate the typical workflow from the experienced one:
-
Open the panel before the action that triggers the request. Network only captures while it’s open. If you open it afterwards, the request you wanted to see is already gone. Open it first, then click. It’s easy to forget the first dozen times.
-
Turn on Preserve log. By default a redirect or a full-page navigation wipes the request log. With Preserve log on, every request stays visible across navigations and reloads. Turn it on as a permanent default in your DevTools settings; you’ll never want it off.
-
Tick Disable cache while DevTools is open. Otherwise the cached request returns in 1ms, and you’ll conclude your change worked when nothing actually went over the wire. The next user, on a cold cache, will still see the old behavior. Keep the cache off whenever the panel is open.
-
Throttle to “Slow 4G” or “3G” when testing loading states. The 2026 presets are “Fast 4G”, “Slow 4G”, “3G”, and “Offline”; Chrome 130+ renamed the old “Fast 3G” and “Slow 3G” presets to match modern device baselines. When something looks fine on localhost, flip to Slow 4G, reload, and watch the skeleton states actually appear. A per-request override (right-click a row → Override request → Throttle) shipped in late 2025, useful for testing one slow API call against an otherwise fast page.
-
Filter by type to drop the noise. Doc shows just the page navigation requests, and Fetch/XHR shows your application’s API calls. Most SaaS debugging happens in Fetch/XHR, and the chip cuts the waterfall from hundreds of rows to the dozen that matter.
-
Read the right pane in this order. Start with Headers (auth, content type, status), then Payload (what your client sent), then Response (what the server sent back, with the Preview tab pretty-printing JSON for you), then Timing (where the time went, which maps onto the four stages from lesson 1 of this chapter). Any other order tends to waste clicks.
-
Right-click → Copy → Copy as fetch / Copy as cURL. This turns a captured browser request into a reproduction you can debug. Paste the fetch into the Console and re-run it, or paste the cURL into a terminal and re-run it. Either way you have a JavaScript or shell version of the exact request the browser sent, ready to edit. The full
fetchAPI gets its own chapter later in this unit; this is your first look at the shape it produces.
Open Network on the page in front of you. Tick Preserve log and Disable cache, then reload. Find the document request row at the top, right-click it, and choose Copy → Copy as fetch. Paste it into the Console (you’ll learn the Console properly in the next section). What you just pasted is the same request the browser made, expressed as JavaScript you can edit and re-run.
Console: what does the page think it knows?
Section titled “Console: what does the page think it knows?”The Console is a REPL inside the running page. Anything in the global scope is reachable, anything any script logs with console.log lands here, and anything you type runs in the page’s own JavaScript context. It’s also the most under-used tool in DevTools, because most newcomers call console.log() and stop there. The Console has a whole vocabulary beyond that, and learning it gives you more for your effort than anything else on this tour.
console.table() output — array of objects rendered as a real columned table.
Seven moves to internalize:
-
Log levels and filtering.
console.logis the default, whileconsole.info,console.warn, andconsole.errorprint at their named levels. The dropdown at the top of the panel lets you hide everything but errors when a page floods the output. For debugging output you want to keep, useconsole.warnandconsole.errorso the important lines stay filterable amid the noise. -
console.table(arrayOfObjects). Renders a real table with one column per object key. Use this instead ofconsole.logfor any array of objects. It saves you from expanding[Object, Object, Object, ...]rows one at a time, which adds up over a career. -
console.dir(domNode). Prints the full JavaScript property tree of a DOM node, including its properties, methods, and internals, rather than the rendered HTML you get fromconsole.log(node). Reach for it when the question is “what JavaScript properties does this node carry?” rather than “what does it look like in the tree?” -
console.trace(). Prints the stack trace at the call site. Use it when the question is “who called this?” and you don’t want to set a breakpoint to find out. -
$0,$1,$2,$3,$4. References to the last five elements you selected in the Elements panel. Click a node in Elements, switch to Console, and type$0.getBoundingClientRect(). This bridge between the Elements and Console panels is one of the most useful, and one of the most often missed. -
copy(value). Copies any value to your clipboard.copy($0.outerHTML)grabs the live rendered markup of the inspected element.copy(JSON.stringify(state, null, 2))copies any complex value as pretty JSON, ready to paste into a bug report or test fixture. -
Live evaluation as you type. Start typing
document.queryS…and DevTools shows the result of the current expression in a faded preview before you hit Enter. This is handy for refining a selector against the live DOM before pasting it into your code.
Those last few names, $0 through $4, copy, plus $_ for the last evaluated expression, are console utilities . DevTools injects them into the Console’s global scope, so they don’t exist in your application code. Try to use copy() in a script file and you’ll get a ReferenceError. That’s worth remembering when something works in the Console but not in a .js file: the utilities don’t travel.
The vocabulary, in five lines:
console.table(users); // an array of objects rendered as a real tableconsole.dir($0); // full JS property tree of the inspected elementconsole.trace(); // stack at this linecopy($0.outerHTML); // live rendered markup → clipboardcopy(JSON.stringify(state)); // snapshot any value as pretty JSONTry the bridge right now. Switch to Elements and inspect any element on the page. Switch to Console and type $0; the element prints. Type console.dir($0) to see its full JavaScript surface unfold. Type copy($0.outerHTML) and paste anywhere; the live rendered markup is on your clipboard.
Application: what’s in storage right now?
Section titled “Application: what’s in storage right now?”The Application panel is the storage and identity inspector. Every place the page persists data is here, including Cookies, Local Storage, Session Storage, IndexedDB, Cache Storage, and Service Workers. Every entry can be inspected, edited, or deleted from the panel.
For day-to-day SaaS work, the two that matter are Cookies, where session cookies live once the auth chapters land, and Local/Session Storage, where client-state tooling and URL-state lists keep their working values. The rest you’ll see once or twice over the course of a project, so they’re named here just to tell you where to find them.
HttpOnly, Secure, SameSite, Expires, Domain, Path.
Four regions to know:
- The sidebar with Manifest, Service Workers, Storage (and its sub-items Local Storage / Session Storage / IndexedDB / Cookies / Cache Storage), and Background Services.
- The Clear site data button under Storage at the top of the section.
- The Cookies row in Storage; when selected, the right pane lists every cookie with its full attribute set.
- The right pane’s cookie attribute columns:
HttpOnly,Secure,SameSite, Expires, Domain, Path.
Five moves to know:
-
Cookies, the auth surface. Expand Cookies under Storage and click the origin you care about. The right pane lists every cookie with its full attribute set: name, value, domain, path, expires/max-age,
HttpOnly,Secure,SameSite. Edit a value in place, double-click any attribute to flip it, or delete a row with the Delete key. When the auth chapters arrive later in the course, this is where you’ll read the session cookie, and when auth doesn’t work, the cookie’sSameSiteandSecureflags are worth checking before the server logs. What those attributes mean, when they apply, and why they block a request gets its own chapter later in this unit. -
Local Storage and Session Storage. Same edit-and-clear surface, keyed by origin. Local Storage persists across tabs and reloads, while Session Storage is per-tab and clears when the tab closes. When the URL-state list project arrives later in the course, the values you’re stashing show up here.
-
IndexedDB. A persistent client-side database, shown here. When offline state matters on a project, this is the panel for it. It isn’t on the daily path for the 2026 SaaS stack, since the platform’s data fetching covers most of what you’d otherwise reach for it.
-
Service Workers and Cache Storage. Service workers register under their own sidebar entry, and whatever they’ve cached shows in Cache Storage. They’re named here so you can find them. The course doesn’t ship a service worker on this stack, because Server Components and the platform’s data fetching cover the use cases.
-
Clear site data , the reset button. One button at the top of the Storage section wipes cookies, every kind of storage, cache, and service workers for the current origin. It’s the fix for “it works in incognito but not here”: your local state has drifted from the server’s expectations, and clearing it is faster than diffing it. Reach for it freely.
Try the cookie inspector right now. Open Application → Storage → Cookies on the page in front of you, then click your origin and read the attribute columns on any cookie. Look at SameSite, Secure, and HttpOnly. The wrong combination of those is why an authenticated request can quietly drop its cookie on a cross-site fetch. The chapter later in this unit covers the production defaults for those attributes; the Application panel is where you’ll diagnose them when something goes wrong.
Sources: where is this client code actually stopping?
Section titled “Sources: where is this client code actually stopping?”The Console reads the page’s state at whatever moment you ask. The Sources panel lets you freeze the page mid-execution and read everything at once. Open Sources, find a client-side script in the file tree on the left, and click the line number in the gutter; a blue marker appears. That marker is a breakpoint: the browser will pause execution the instant it reaches that line, before it runs it, and hand you the frozen page.
You don’t always need the gutter, though.
A bare debugger; statement does the same thing from inside the code itself, pausing execution at that line whenever DevTools is open and doing nothing at all when it isn’t.
It’s the fastest way to stop on a line you’re already editing, with one rule: delete it before you commit, the same way you’d delete a stray console.log.
function applyDiscount(cart, code) { debugger; // execution pauses here when DevTools is open const rate = lookupRate(code); return cart.total * (1 - rate);}Once you’re paused, three buttons drive the frozen frame. Step over runs the next line and stops again, treating any function call on it as a single step. Step into follows a function call down into its body so you can watch what happens inside. Resume lets the page run on until the next breakpoint or until it finishes.
The payoff sits in the right-hand pane while you’re paused.
The Scope pane lists every variable in reach at the current frame, locals and closures and globals, with their live values; that’s the whole point over a single console.log, which only shows the one thing you remembered to print.
The Watch pane lets you pin an expression, say cart.total, and read its value at every pause without retyping it.
Try it on the page in front of you: open Sources, set a breakpoint on any line of a script that runs on a click, then click and watch the page freeze with the Scope pane filled in. This is the browser-side beat only. Stepping through server-side code, where most of this stack’s logic actually runs, is a different workflow that the error-monitoring and observability chapter near the end of the course owns; the Console stays your default for everyday client-side checks.
The panels we’re deliberately skipping
Section titled “The panels we’re deliberately skipping”Map a scenario to a panel
Section titled “Map a scenario to a panel”Here is one last drill. Read each scenario and decide which panel you’d open first. Each scenario maps to one panel, and some panels show up more than once.
For each production-shaped scenario, click the panel you would open first. Click an item on the left, then its match on the right. Press Check when done.
SameSite and Secure attributeslocalStorage data they shouldn’t havecopy($0.outerHTML) after inspecting in Elementsconsole.trace() at the call siteWhat stays out of this lesson
Section titled “What stays out of this lesson”This is a survey, not a manual, and each panel has deeper material covered elsewhere.
- Cookie attribute semantics, namely
HttpOnly,Secure, andSameSite, are taught in their own chapter later in this unit. The Application panel surfaces them; the chapter explains them. - The CSS cascade itself, specificity, the box model, and Tailwind layer ordering arrive in the styling unit later in the course. Elements is where you’ll read them.
- The
fetchAPI, request and response shapes, and error handling are covered later in this unit. The Network panel reads them. - React component tree inspection, prop edits via DevTools, and Profiler flame charts are covered in the React unit and again in the performance unit. React DevTools is installed now; the depth waits.
- Core Web Vitals, Performance flame charts, Lighthouse audits, and bundle analyzers are covered in the performance unit. The panels exist now; the workflow lives there.
You now have the layout in your head, and the depth comes from doing the work. The next lesson pushes that along with a local HTTPS setup that unblocks the secure-context APIs the rest of this unit depends on.
External resources
Section titled “External resources”The canonical reference for every DevTools panel. Skim the panel docs as you start using each one in earnest.
Monthly changelog of new DevTools features. Worth bookmarking once you're past the basics, since the panels gain new tools every release.
The cross-browser sanity check. Concepts transfer from Chromium; the layout differs.
The official guide to the Components and Profiler tabs you just installed. Bookmark for when the React unit lands.