Debugging a Slow or Broken Page
Debugging a Slow or Broken Page
The Triage Order
Resist the urge to jump straight into the Debugger. Start broad and work inward. Most bugs are resolved by step 2 or 3.
Part 1 — Visual & Layout Bugs
When an element is missing, invisible, or in the wrong place, the Inspector is the first stop. Right-click the element → Inspect Element to jump directly to it in the DOM.
Reading the Rules panel
In the Inspector, the right-hand Rules panel lists every CSS rule that applies to the selected element, in order of specificity — the topmost rule wins. Overridden properties are shown with a strikethrough.
The strikethrough on display: none means it was overridden. If you see display: none without a winning override, the JavaScript that should add the .active class never ran — check the Console for errors.
display: none, visibility: hidden, opacity: 0, or being clipped by overflow: hidden on a parent. All are visible in the Rules panel.
Responsive design bugs
Open Responsive Design Mode (Ctrl + Shift + M) to resize the viewport. As you drag the width, watch the Rules panel — @media rules grey out when their condition is not met. Click any @media label to jump to the breakpoint declaration in the Style Editor.
Part 2 — JavaScript Errors
Open the Console. JavaScript errors appear in red and always include a clickable stack trace — use it.
This tells you: something at app.js:142 tried to read .email from undefined. Click the link — Firefox opens the exact line in the Debugger. The object you expected to exist (a user object from an API call) was never populated — likely because the API call failed or returned an unexpected shape.
Common error signatures
| Console message | Likely cause |
|---|---|
Cannot read properties of undefined (reading 'X') | Object wasn't initialised — API returned an unexpected shape, or data hasn't loaded yet |
X is not a function | You called something that isn't a function — variable shadowing, or a typo |
ReferenceError: X is not defined | Variable used before declaration, or a typo in the name |
Uncaught (in promise) Error: … | Async function threw with no .catch() or try/catch |
SyntaxError: Unexpected token '<' | A fetch() expected JSON but got HTML — check the Network panel Response body for a 404 page |
Failed to fetch | fetch() failed — check the Network panel for the corresponding request status |
Catching async errors in the Debugger
When the Console shows "Uncaught (in promise)", click the Exception breakpoint icon (⬡ hexagon) in the Debugger toolbar → select Pause on uncaught exceptions. Trigger the failing action — the Debugger pauses at the exact throwing line, with the full call stack visible on the right.
Part 3 — Slow Page Loads
Open the Network panel. Click the Reload button (🔄) in the toolbar to capture the full page load. Look at the DOMContentLoaded (blue line) and Load (red line) markers at the bottom of the request list.
Waterfall anatomy — a slow load
Everything after app.bundle.js starts only after the script finishes — the yellow bar consumes 68% of the waterfall timeline. Adding defer to the script tag would allow the HTML to parse in parallel, moving DCL to under 500ms.
Diagnosing slow requests — TTFB vs payload
Sort the Network panel by Time descending. Click the slowest request → Timings tab. The question is: is the time dominated by Waiting (server is slow) or Receiving (payload is large)?
Part 4 — Runtime Performance (Jank & Freezes)
When the page loads fine but slows down during use — scroll jank, a frozen UI after a click, an animation that stutters — the Performance panel is the right tool. Open it with Ctrl + Shift + P.
A performance recording with a long task
The red section in the FPS bar corresponds to the long JS task (marked ▲). The Bottom-Up tab immediately names filterProducts at app.js:214 as the most expensive function — that's where to look first.
Layout thrashing — the pattern
The repeated Layout bars inside the JS task above are a sign of layout thrashing: JavaScript reads a layout property after changing the DOM, forcing the browser to recalculate layout synchronously on every iteration.
// Reads offsetWidth AFTER each write elements.forEach(el => { // write, then immediate read el.style.width = container.offsetWidth + 'px'; });
// Read once before the loop const w = container.offsetWidth; elements.forEach(el => { el.style.width = w + 'px'; });
The Bottom-Up shortcut
After recording any profile, open the Details panel → Bottom-Up tab → sort by Self Time descending. The top entry is the function doing the most work. Click it to jump to it in the flame chart. This is always faster than reading the flame chart left-to-right.
Cross-Panel Workflow — A Real Example
Scenario: "The dashboard loads but freezes every time I click 'Load More'."
TypeError or ReferenceError here often explains the whole problem. If no errors — continue.JSON.parse or a filter/sort function, the response payload is too large or the processing is unoptimised.loadMore function. Step through with F10. Watch the Scopes panel — at which line does the data become unexpectedly large, or does an operation take multiple iterations over thousands of items?Practical Tips
$0. This gives you a live JavaScript reference to that element. Call methods, read properties, check its current dimensions — all without writing setup code: $0.getBoundingClientRect()
F5 = normal reload (uses cache). Ctrl + Shift + R = hard reload (ignores cache). Always use Ctrl + Shift + R when investigating resource loading — otherwise you may be looking at a cached version of a file you already fixed.
Exercises
click? What file and line number is it on? Click the link — does it open in the Debugger?
document.querySelectorAll('img').forEach((img, i) => { if (!img.complete || img.naturalHeight === 0) console.warn(`Image ${i} failed:`, img.src); });Are any images broken? Switch to the Network panel → filter by Images to confirm.
Ctrl + Shift + P). Click Record, scroll the page for 3 seconds, stop. Are there any red sections in the FPS bar? Click into one → open the Bottom-Up tab. What function has the highest Self Time?
performance.getEntriesByType('navigation')[0]Read the
domContentLoadedEventEnd, loadEventEnd, and transferSize values. What do they tell you about this page's load performance?
Shift + F11 (Step Out) to jump back up the call stack. Uncheck the breakpoint when done.
Key Takeaways
- Triage in order — Console → Network → Inspector → Debugger → Performance. Most bugs are solved before step 4.
- Stack traces are clickable — click any
file.js:linein a Console error to jump directly to the Debugger at that line. SyntaxError: Unexpected token '<'after a fetch means the server returned HTML (an error page) instead of JSON — check the Network Response body.- Sort Network by Size to find large resources; sort by Time to find slow ones. Timings tells you whether the delay is TTFB (server) or Receiving (payload).
- Render-blocking scripts without
deferorasyncin<head>block parsing — visible as a gap in the waterfall where nothing else starts. - Layout thrashing (read → write → read → write in a loop) appears as repeated purple Layout bars inside a JS task. Fix: read layout properties once, outside the loop.
- Bottom-Up → Self Time is the fastest route to the most expensive function in any performance recording.
- Event Listeners tab in the Inspector shows every handler on a selected element with a direct link to the Debugger.
$0in the Console gives you a live reference to the element currently selected in the Inspector.- Disable Cache in the Network toolbar ensures you always see real load times, not cached results.