{"course":{"slug":"building-apps-with-ai","title":"Building Apps with AI","url":"https://addaly.com/learn/building-apps-with-ai","level":"beginner","track":"build"},"lesson":{"slug":"classifying-the-failure","title":"Classifying the failure before you fix it","url":"https://addaly.com/learn/building-apps-with-ai/classifying-the-failure","position":30,"of":87,"minutes":8,"answer":"Place a failure in the browser, the server, the database or the network before diagnosing it, because a silent wrong answer and a 500 need completely different first moves.","keyIdea":"Place a failure in the browser, the server, the database or the network before diagnosing it, because a silent wrong answer and a 500 need completely different first moves."},"body":{"format":"markdown","text":"## Where, before what\n\nA broken app gives you a symptom. The useful first move is not to guess a cause but to place the failure in one of five zones, because each zone has one place to look and you can reach the right one in under a minute.\n\n## The blank white page\n\nNothing rendered. Almost always a JavaScript error during the first render.\n\nOpen the console. There will be a red line, and it is usually the only one that matters: everything below it is a consequence. The most common cause by far is reading a property of something that has not arrived yet, which means the component assumed data it did not check for.\n\nA blank page with a *clean* console is different: the request may have returned an empty list and the component has no empty state. Check the network panel before assuming a crash.\n\n## The 500 page\n\nYour server code threw. The user sees nothing useful on purpose.\n\nLook in the terminal running the dev server, or your host's logs in production. The stack trace is there in full. If your logs show nothing at all at that moment, the request may never have reached your code: check the URL and the status code in the network panel, because a 404 dressed up as a generic error page looks identical to the user.\n\n## The 404\n\nNo route matched. Three usual causes, in order of frequency: a typo in the path, a file in the wrong folder so the framework never registered it, and a case mismatch that works locally and fails on the Linux server.\n\nIf the page exists but a `fetch` inside it 404s, read the exact path in the network panel. A leading slash missing turns `/api/bookings` into a relative path that resolves somewhere else entirely.\n\n## The 401 and the 403\n\nThese are different and the difference is the diagnosis. **401** means the server does not know who you are: the session is missing or expired, or a cookie is not being sent. **403** means it knows and is refusing: you are logged in as the wrong person, or a permission check is stricter than intended.\n\nGenerated code frequently returns one where it means the other, which sends you looking in the wrong half of the system. Worth checking that the code returning it is honest.\n\n## The silent wrong answer\n\nNo error anywhere. A number is wrong, a list is missing three rows, a total does not add up. This is the dangerous category, because nothing announces it and it can sit in production for months.\n\nThere is no log to read. The move is to ask the database the same question directly and compare with the screen. Whichever side disagrees with your expectation is the side to investigate, and that single comparison usually cuts the problem in half.\n\n## It works here and not there\n\nSame code, different behaviour on your laptop and in production. The zone is configuration, not logic, and the shortlist is short: a missing environment variable, a database that has not had its migrations run, filename capitalisation, a Node version difference, or a build step that behaves differently from the dev server.\n\nA whole lesson later in this block is about this one, because it accounts for a large share of first deployments.\n\n## Ask the classifying question first\n\nBefore you paste anything anywhere:\n\n> Did this happen in the browser, on the server, in the database, or in the network between them?\n\nThirty seconds with the console and the network panel answers it, and the answer determines everything after. A great many long, frustrating debugging sessions are somebody looking in the browser for a problem that happened on the server.\n\n## Write the classification into the request\n\n```\nServer-side 500 on POST /api/bookings, every time, since I added\nthe overlap check. Terminal shows:\n[first three lines]\nBrowser console is clean. The request body looks correct in the\nnetwork panel. Nothing changed in the database.\n```\n\nThat is a diagnosis handed over, not a symptom. It tells the model where not to look, which is worth as much as telling it where to look, and it prevents the response that begins by rewriting your React component for a problem that never touched the browser.\n\n> Figure: Where did this happen. A two by two grid, rows Console has a red line and Console is clean, columns A request failed and Every request is 200: Console has a red line and A request failed: Both; Console has a red line and Every request is 200: Browser; Console is clean and A request failed: Server; Console is clean and Every request is 200: Silent. Thirty seconds with the console and the network panel places any failure in one of these boxes, and the box decides where to look. The bottom right has no log to read at all."},"check":{"question":"A page renders blank and the browser console shows no errors. What does that combination suggest?","options":[{"text":"A JavaScript crash occurred before the console could attach.","correct":false,"tempting":"Invents a timing gap; devtools capture errors from the first script, and a crash leaves a message."},{"text":"The server returned a 500 and the browser suppressed it.","correct":false,"tempting":"A 500 replaces the page with an error response rather than rendering a blank component tree silently."},{"text":"The request probably succeeded and returned nothing, and the component has no empty state.","correct":true},{"text":"The CSS is hiding the content, so the elements exist but are invisible.","correct":false,"tempting":"Possible and rarer; it is worth checking in the elements panel, but an unstyled empty result is the far more common shape."}]},"prev":{"slug":"anatomy-of-an-error-message","title":"The anatomy of an error message","url":"https://addaly.com/learn/building-apps-with-ai/anatomy-of-an-error-message"},"next":{"slug":"finding-the-change-that-broke-it","title":"Finding the change that broke it","url":"https://addaly.com/learn/building-apps-with-ai/finding-the-change-that-broke-it"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Classifying the failure before you fix it', in Building Apps with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-apps-with-ai/classifying-the-failure (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Classifying the failure before you fix it","courseTitle":"Building Apps with AI","module":{"index":4,"slug":"when-it-breaks","title":"When it breaks, and getting back"},"answer":null,"keyIdea":"Place a failure in the browser, the server, the database or the network before diagnosing it, because a silent wrong answer and a 500 need completely different first moves.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"cols":["A request failed","Every request is 200"],"rows":["Console has a red line","Console is clean"],"type":"matrix","cells":[["Both","Browser"],["Server","Silent"]],"title":"Where did this happen","caption":"Thirty seconds with the console and the network panel places any failure in one of these boxes, and the box decides where to look. The bottom right has no log to read at all.","cellLabels":[["the fetch failed first","it crashed on render"],["read the server log","ask the database"]]},"alt":"Figure: Where did this happen. A two by two grid, rows Console has a red line and Console is clean, columns A request failed and Every request is 200: Console has a red line and A request failed: Both; Console has a red line and Every request is 200: Browser; Console is clean and A request failed: Server; Console is clean and Every request is 200: Silent. Thirty seconds with the console and the network panel places any failure in one of these boxes, and the box decides where to look. The bottom right has no log to read at all."}},"blocks":[{"t":"h2","id":"where-before-what","runs":[{"text":"Where, before what"}]},{"t":"p","runs":[{"text":"A broken app gives you a symptom. The useful first move is not to guess a cause but to place the failure in one of five zones, because each zone has one place to look and you can reach the right one in under a minute."}]},{"t":"h2","id":"the-blank-white-page","runs":[{"text":"The blank white page"}]},{"t":"p","runs":[{"text":"Nothing rendered. Almost always a JavaScript error during the first render."}]},{"t":"p","runs":[{"text":"Open the console. There will be a red line, and it is usually the only one that matters: everything below it is a consequence. The most common cause by far is reading a property of something that has not arrived yet, which means the component assumed data it did not check for."}]},{"t":"p","runs":[{"text":"A blank page with a "},{"text":"clean","i":true},{"text":" console is different: the request may have returned an empty list and the component has no empty state. Check the network panel before assuming a crash."}]},{"t":"h2","id":"the-500-page","runs":[{"text":"The 500 page"}]},{"t":"p","runs":[{"text":"Your server code threw. The user sees nothing useful on purpose."}]},{"t":"p","runs":[{"text":"Look in the terminal running the dev server, or your host’s logs in production. The stack trace is there in full. If your logs show nothing at all at that moment, the request may never have reached your code: check the URL and the status code in the network panel, because a 404 dressed up as a generic error page looks identical to the user."}]},{"t":"h2","id":"the-404","runs":[{"text":"The 404"}]},{"t":"p","runs":[{"text":"No route matched. Three usual causes, in order of frequency: a typo in the path, a file in the wrong folder so the framework never registered it, and a case mismatch that works locally and fails on the Linux server."}]},{"t":"p","runs":[{"text":"If the page exists but a "},{"text":"fetch","code":true},{"text":" inside it 404s, read the exact path in the network panel. A leading slash missing turns "},{"text":"/api/bookings","code":true},{"text":" into a relative path that resolves somewhere else entirely."}]},{"t":"h2","id":"the-401-and-the-403","runs":[{"text":"The 401 and the 403"}]},{"t":"p","runs":[{"text":"These are different and the difference is the diagnosis. "},{"text":"401","b":true},{"text":" means the server does not know who you are: the session is missing or expired, or a cookie is not being sent. "},{"text":"403","b":true},{"text":" means it knows and is refusing: you are logged in as the wrong person, or a permission check is stricter than intended."}]},{"t":"p","runs":[{"text":"Generated code frequently returns one where it means the other, which sends you looking in the wrong half of the system. Worth checking that the code returning it is honest."}]},{"t":"h2","id":"the-silent-wrong-answer","runs":[{"text":"The silent wrong answer"}]},{"t":"p","runs":[{"text":"No error anywhere. A number is wrong, a list is missing three rows, a total does not add up. This is the dangerous category, because nothing announces it and it can sit in production for months."}]},{"t":"p","runs":[{"text":"There is no log to read. The move is to ask the database the same question directly and compare with the screen. Whichever side disagrees with your expectation is the side to investigate, and that single comparison usually cuts the problem in half."}]},{"t":"h2","id":"it-works-here-and-not-there","runs":[{"text":"It works here and not there"}]},{"t":"p","runs":[{"text":"Same code, different behaviour on your laptop and in production. The zone is configuration, not logic, and the shortlist is short: a missing environment variable, a database that has not had its migrations run, filename capitalisation, a Node version difference, or a build step that behaves differently from the dev server."}]},{"t":"p","runs":[{"text":"A whole lesson later in this block is about this one, because it accounts for a large share of first deployments."}]},{"t":"h2","id":"ask-the-classifying-question-first","runs":[{"text":"Ask the classifying question first"}]},{"t":"p","runs":[{"text":"Before you paste anything anywhere:"}]},{"t":"quote","runs":[{"text":"Did this happen in the browser, on the server, in the database, or in the network between them?"}]},{"t":"p","runs":[{"text":"Thirty seconds with the console and the network panel answers it, and the answer determines everything after. A great many long, frustrating debugging sessions are somebody looking in the browser for a problem that happened on the server."}]},{"t":"h2","id":"write-the-classification-into-the-request","runs":[{"text":"Write the classification into the request"}]},{"t":"code","lang":"","label":"Text","text":"Server-side 500 on POST /api/bookings, every time, since I added\nthe overlap check. Terminal shows:\n[first three lines]\nBrowser console is clean. The request body looks correct in the\nnetwork panel. Nothing changed in the database."},{"t":"p","runs":[{"text":"That is a diagnosis handed over, not a symptom. It tells the model where not to look, which is worth as much as telling it where to look, and it prevents the response that begins by rewriting your React component for a problem that never touched the browser."}]}],"check":{"question":[{"text":"A page renders blank and the browser console shows no errors. What does that combination suggest?"}],"options":[{"text":[{"text":"A JavaScript crash occurred before the console could attach."}],"correct":false,"tempting":[{"text":"Invents a timing gap; devtools capture errors from the first script, and a crash leaves a message."}]},{"text":[{"text":"The server returned a 500 and the browser suppressed it."}],"correct":false,"tempting":[{"text":"A 500 replaces the page with an error response rather than rendering a blank component tree silently."}]},{"text":[{"text":"The request probably succeeded and returned nothing, and the component has no empty state."}],"correct":true,"tempting":null},{"text":[{"text":"The CSS is hiding the content, so the elements exist but are invisible."}],"correct":false,"tempting":[{"text":"Possible and rarer; it is worth checking in the elements panel, but an unstyled empty result is the far more common shape."}]}],"order":[1,3,0,2],"why":null},"prev":{"slug":"anatomy-of-an-error-message","title":"The anatomy of an error message","minutes":8},"next":{"slug":"finding-the-change-that-broke-it","title":"Finding the change that broke it","minutes":8}}}