Hands-On Test of the 'Ban Axios' Argument: Fetch Covers the Basics, but Three Unmentioned Traps Decide the Migration
The cost of dropping axios isn't in the feature comparison; it's the silent 404/500 behavior and the refresh race that will surface as production incidents. Those two alone turn an otherwise clean migration into a full regression testing exercise.
A direct line-by-line test of the 'ban axios' claims against axios 1.20.0 and Node's native fetch on v22.23.2 confirms timeout and cancellation behave almost identically: a 500ms limit on a 3-second endpoint cut off at 517ms with axios and 504ms with fetch, with different error types (AxiosError vs TimeoutError). CancelToken still works but throws CanceledError and is deprecated.
The real differences surface in three unmentioned places. Request interceptors execute in reverse registration order, so three handlers registered A, B, C run C, B, A; that stack order breaks any handler that depends on a previous one's output. More critically, axios rejects on HTTP errors while fetch resolves with res.ok false, so existing catch-based error toasts never fire on a 404 or 500, and undefined data flows into rendering until the page white-screens. The costliest trap is a race in the common token-refresh pattern: a single global refreshing variable prevents duplicate refresh calls only when 401s arrive in the same event loop tick. When they spread across ticks, a second 401 sees refreshing cleared and triggers another refresh; on the same logic, axios's interceptor chain spread the five 401s across async boundaries and fired three refreshes while fetch fired one. The fix is to capture the token used for the request and compare it before re-fetching, which brings both clients down to a single refresh.
The reverse interceptor order is a documented behavior that feels like a bug; it explains many 'impossible' debugging sessions where a logging interceptor sees undefined.
The semantic difference on HTTP errors is the real migration blocker: existing catch blocks are written for network failures, so business errors vanish silently.
The refresh race is library-agnostic; common open-source templates carry it, and it only becomes visible under certain timing patterns.
The test itself shows that micro-benchmarks can mislead: the same fetch logic produced one refresh in one timing and two in another, depending on how 401 responses are batched.