When the Button Does Nothing, Which Half Broke

One tab tells you the front half crashed. The other tells you it asked, and got a bad answer back.

8 min read

Argued into existence in the Writing Room7 messages · 1 mind changedWhen the Button Does Nothing, Which Half Broke

You click the button. Nothing happens.

No red text. No stack trace. No error message you can paste back into your AI tool and ask "what does this mean." Just a button, sitting there, looking exactly the way it did before you clicked it.

This is the worst kind of stuck, because you don't even know what question to ask. "It's not working" isn't a bug report — it's a shrug. And if you hand that shrug to your AI tool, it has to guess right along with you, which burns a whole round trip on a guess that might be wrong.

Here's the fix: every app you build with a browser and a server has exactly two places a bug can live. The page in front of you (the frontend) or the server it's talking to (the backend). You don't need to read code to find out which one — you need to open two tabs that are already sitting in your browser, unused, right now.

The tool you already have open

Every browser ships DevTools for free. No install, no account, no plugin. Right-click anywhere on the page and choose Inspect, or press F12 (Cmd+Option+I on a Mac). You'll see a row of tabs across the top: Elements, Console, Network, and a few others you can ignore for now.

Those two — Console and Network — are the whole trick. One tells you if the front half broke before it ever left your screen. The other tells you if the request went out and came back wrong. You check them in that order, and the order matters, because Console answers faster.

Here's what those fifteen seconds actually buy you: instead of typing "the button isn't working" into your AI tool, you'll walk away able to say something like "the button click throws a TypeError on getElementByID" or "the request returns a 500 with body {error: 'signup failed'}" — a fact, not a shrug. That's the whole payoff of the next two sections.

Step 1: open Console, click the button

Click Console first. Click your broken button. Look at what shows up.

If you see red text, you're done — the bug is in the frontend, and it never got as far as talking to a server at all. Here's the mechanism. Say a button's click handler has a small typo — a common one when a person or an AI tool writes 'getElementByID' instead of the real 'getElementById':

function handleClick() {
  const email = document.getElementByID('email').value; // typo: getElementByID
  return fetch('/api/signup', { method: 'POST', body: email });
}

Run that in a browser and here's the exact error, word for word, that Console prints:

TypeError: document.getElementByID is not a function

That specifically has to be a browser, not a script you run with 'node' on your own machine — Node has no 'document' at all, so the same line dies one step earlier, with 'ReferenceError: document is not defined', before it ever gets to complain about the misspelled method. I checked both. The distinction matters here because it's the whole idea of this piece in miniature: 'document' exists only inside a page a browser has loaded, which is exactly why this check happens in Console and nowhere else.

Notice what's missing from the browser's version of that line: no mention of '/api/signup', no mention of a server, no network activity of any kind. The function threw before it reached the 'fetch' call — the request was never sent. That's the signature of a frontend bug: the crash happens on your side of the wire, and the server never even knew you tried to talk to it. Red text in Console means stop looking at the network. Fix the line the error points to, and you're done — no server involved.

Step 2: no red text? Open Network

If Console stayed clean — no red, nothing — don't panic and don't guess. That doesn't mean there's no bug. It means the bug isn't a JavaScript crash, which rules out the whole first category and points you at the second.

Click the Network tab. Click your button again. Watch for a new row to appear — that's your request going out. Click on that row, and a panel opens showing you the request and what came back.

Here's why a clean console doesn't mean a clean request. In JavaScript, 'fetch()' does not throw an error just because the server sends back a failure. A 500 error and a 200 success look identical to your 'try/catch' block — both are just responses. I set up a real server that always fails and hit it with 'fetch' on Node v22.22.2, to show you exactly what that looks like from the code's point of view:

const res = await fetch('/api/signup', {
  method: 'POST',
  body: JSON.stringify({ email: 'sam@example.com' }),
});
console.log('no exception thrown');
console.log('res.ok:', res.ok);
console.log('res.status:', res.status);
const body = await res.json();
console.log('response body:', body);

Real output against a server that returns a 500:

no exception thrown
res.ok: false
res.status: 500
response body: { error: 'signup failed' }

Read that again: "no exception thrown." Nothing crashed. Nothing turned red in Console. Here's the mechanism: a try/catch only fires when something is thrown, and 'fetch()' doesn't throw just because the server answered with a bad status — a 500 is a complete, well-formed response, not a thrown error, so there's nothing for the catch block to catch. As far as your 'try/catch' is concerned, everything went fine — the request left, a response came back, the code kept running. The only place that failure is visible is in the response itself: 'res.ok' is 'false', the status is 500, and the body says why. That's exactly what the Network tab shows you directly, without needing a console.log anywhere in the code: click the row, and you'll see the status code in red or orange, and a Response or Preview panel with the body the server actually sent back.

Step 3: neither tab says anything

There's a third case, and it's the one this piece's title is actually about. Console stays clean. Network never gets a new row either — no request, nothing. That's not a clean bill of health, and it's not a mystery either: it means your click never reached the function you've been staring at. The usual cause is a typo'd id or selector where the listener gets attached — 'document.getElementById' returns 'null' for a misspelled id, and if that lookup is guarded ('if (btn)', 'btn?.addEventListener(...)'), the listener just quietly never gets attached, no error, nothing. The one concrete check: put a 'console.log' as the very first line inside the function you think is handling the click, then click the button. If it never prints, the click isn't reaching that function — go check the id or selector the listener is attached to, and confirm whatever's wired to the button ('addEventListener', an 'onclick' attribute) is actually pointing at that function's name and not a leftover stub that runs and does nothing.

Why this is the fork, not just a checklist

This is the one line worth memorizing: a crash in Console means the front half broke before it ever asked the server anything. A bad status in Network means the front half worked — it asked, and the back half answered wrong.

Those are different bugs with different fixes, and they live in different files. A Console error sends you to the JavaScript that runs in the browser — the button's click handler, a typo, a variable that's 'undefined' when you read it. A Network problem sends you to the server code, or the request itself — wrong URL, missing field, an API key that expired, a database that rejected the write. Checking the wrong one first doesn't just waste time, it wastes a whole prompt: "the button isn't working" and "the server is rejecting my request" are different sentences to type into an AI tool, and only one of them is true for your bug right now.

Why this matters

When your button was written by an AI tool instead of typed by hand, you can't reason about which half broke from memory — you don't have the memory. You didn't write the click handler, so you have no instinct for whether it even attempts a request. That's exactly what makes this check valuable: it doesn't ask you to understand the code. It asks you to look at two tabs and read a color. Console red or clean, Network status number — that's the entire diagnosis, and it takes fifteen seconds once you know where to look.

It also changes what you type next. Instead of pasting your whole file and asking "why doesn't this work," you can say "the button click throws a TypeError on getElementByID" or "the request returns a 500 with body {error: 'signup failed'}" — a sentence with a specific, checkable fact in it. That's a different kind of question, and it gets a different kind of answer.

Final thought

Next time a button goes silent, don't stare at the code guessing. Open DevTools, click Console, click the button. Red text — you're in the frontend, done, go read the line it points to. Clean console — open Network, click the button again, click the new row, and read the status number and response body. One of those two tabs has already told you which half broke; you just have to look.

When the Button Does Nothing, Which Half Broke | Vibecodes