Skip to content

What Should My App Show When a Request Fails Instead of Just Breaking?

6 min read

When a request in your app fails, it should show a calm message the user can act on: a short "something went wrong, please try again" with a retry button, not a frozen screen, a blank page, or a wall of red technical text. The correct answer is a visible, recoverable error state on every screen that loads or saves data. Most vibe-coded apps don't have one, because the demo never failed.

Why this happens

When you asked your AI agent to "load the user's orders" or "save the form," it wrote the happy path: fetch the data, show the data. That is the only path it was asked for, and on your machine, on good wifi, with a warm database, it always works. So it looks finished.

Real users are not on the happy path. Their connection drops mid-request, your database is briefly asleep, an API key expires, a server returns a 500. When that happens, code that only handles success does one of three ugly things: it hangs on the loading spinner forever, it renders a blank screen because the data it expected is missing, or it throws and shows the browser's raw error. None of those tell the user what to do next. The agent built what you asked for. It did not build the part you didn't mention.

How to check

You can trigger a failure on purpose in about a minute.

  1. Open your app, then open the browser's developer tools and go to the Network tab.
  2. Set the throttling dropdown to Offline.
  3. Now click around: load a page that fetches data, submit a form, open a detail view.
  4. Watch what happens. A spinner that never stops, a page that goes empty, or a red crash all mean you have no error state. A clear message with a way to retry means you do.

Do this on your two or three most important screens: the first screen after login, anything that lists data, and any form that saves. Those are where a failure hurts most.

The fix

  1. Wrap every data call in try/catch (or the error branch of your data hook). If you use React Query, SWR, or similar, read the error and isError values they already return instead of only reading data.
  2. Render three explicit states, not one. Every screen that loads data needs loading, error, and success. Add the error branch: "We couldn't load this right now," plus a Try again button that re-runs the request.
  3. Keep forms usable after a failed save. Do not clear the fields. Show an inline message near the submit button, re-enable the button, and let the user press it again. Losing what they typed is worse than the error itself.
  4. Add a top-level error boundary so one broken component shows a friendly fallback instead of taking down the whole page. In Next.js, an error.tsx file in your route folder does this. Check the version in node_modules/next/dist/docs/ before wiring it up, as the API has changed.
  5. Never show the raw error text to users. Log the real detail for yourself, show a plain sentence to them.
  6. Re-run the offline test. Every important screen should now fail softly and recover when you go back online and hit retry.

The trap to avoid

The common wrong fix is a single global alert() or a toast that says "Error" and nothing else. It interrupts, it disappears, and it leaves the underlying screen just as broken as before. An error state belongs in the place the content should have been, with a retry action attached, so the user can fix it without guessing. A toast is a notification, not a recovery.

Where this fits

A missing error state is invisible in every demo and obvious the first time a real user's connection hiccups. It sits alongside the other "the app only works when everything works" gaps, like why your app flashes blank before it loads and the ugly crash on a page that doesn't exist. The free Readiness Report walks your real app and flags the screens that break instead of failing softly, and the Finishing Pass hands you the fixes in order so your beta users can always recover.