Handbook

Is It My Code, the API, or the Browser?

A request fails in the browser and the console message describes a rule rather than a cause. Before reading any of your own code, three checks will tell you whether the problem is yours, the server's, or the browser's policy — and the order matters, because each one eliminates a whole category.

1. Send the same request without a browser

Reproduce the request exactly — same method, headers, body — outside the browser. If it succeeds there and fails in the page, the request itself is fine and something in the browser's rules rejected it, which is almost always cross-origin policy and occasionally a cached preflight. If it fails there too, the server genuinely returns that, and you can stop reading your own code entirely. This one check splits the problem in half and takes a minute.

2. Read the response, not the status

A 200 with an error document inside it is extremely common and defeats every check that only looks at the code. Format the body and look at it. While you are there, confirm the shape is what your code expects: a field renamed, a string that is now a number, an array that became an object — none of those change the status, and all of them break the consumer.

3. If it is cross-origin, test the preflight specifically

The browser sends a preflight request before certain calls, and the failure is usually in that exchange rather than in the request you think you are making. The recurring causes are specific: a wildcard origin combined with credentials, which the specification forbids outright; a custom header your code adds that is not in the allowed list, which silently converts a simple request into a preflighted one; or a proxy stripping headers the application set correctly.

When the answer is "the browser is using an old copy"

If the behaviour does not match the code that is deployed, check the caching headers on the asset rather than hard-refreshing. A hard refresh bypasses the cache you are trying to diagnose, so it proves nothing about what a normal visitor is getting. An asset with a long lifetime and no fingerprint in its filename will be served from cache to returning visitors for as long as the header says, regardless of what you deployed.

When two things that should be identical are not

Diff them. A signature that fails to verify, a config value that behaves differently between environments, an assertion that fails on strings which look the same on screen — the difference is usually a trailing space, a non-breaking space from a paste, or a line ending. All invisible in an editor, all obvious in a diff, and none of them findable by re-reading the code.

The check that stops you doing this again

Every step above is reconstructing information that could have been recorded. If the API you depend on is monitored for response shape rather than just availability, a contract change announces itself the day it ships instead of presenting as an inexplicable front-end bug a week later.

Tools mentioned here