why-did-you-fetch
live demo — the real published package, running in this page

Catch requests your app never needed to make.

why-did-you-fetch patches fetch/XMLHttpRequest in development and tells you exactly which call site made a request your app didn't need to — the same idea as why-did-you-render, aimed at your network tab instead of your render tree.

Try it

Pick a pattern below. It runs the real init() from the published why-did-you-fetch package against a mock network layer (nothing real leaves your browser) and shows you exactly what you'd see in your own console.

Network— simulated
Console— open your real one too
tuned for this demo: dedupeWindowMs 1500, chainGapMs 50 (defaults: 2000 / 10)

What it catches

Five detectors, run on every patched request. The first two are near-certain; the other three name an observable pattern, not a proven dependency.

DetectorFires whenConfidence
duplicate-inflight The exact same request is issued again before the first one has settled. high
duplicate-recent The exact same request repeats shortly (default 2s) after an identical one just finished. high
sequential-chain 3+ requests fire back-to-back with almost no gap between one settling and the next starting. heuristic
rapid-calls 5+ calls to the same path with a different query string each time fire within 1s — a search box refetching on every keystroke. heuristic
n-plus-one 5+ calls to the same route shape (id-like segments collapsed, e.g. /api/users/:id) each to a different URL fire within 500ms — a list fetching each row's data individually. heuristic

Add it to your project

One call, as early as possible. It no-ops automatically when NODE_ENV === 'production'.

$npm install --save-dev why-did-you-fetch

Configuration

Every option is optional. Full reference in the README.

OptionDefaultPurpose
dedupeWindowMs2000Window for the "recent duplicate" check.
chainGapMs10Max settle→start gap counted as "back-to-back".
chainMinLength3Chain length before it's reported.
rapidCallWindowMs1000Rolling window for the rapid-calls detector.
rapidCallMinCount5Varying-query calls within that window before it's reported.
nPlusOneWindowMs500Rolling window for the n-plus-one detector.
nPlusOneMinCount5Distinct-URL calls to the same route shape within that window before it's reported.
maxInflightAgeMs60000Stop tracking a request that never settles after this long.
ignoreKeepalivetrueSkip fetch(url, { keepalive: true }) beacon/analytics calls.
ignore[]Strings, RegExps, or predicates to skip (analytics beacons, etc).
normalizeBodyidentityTransform a body before duplicate matching (e.g. strip a trace id).
onIssueconsole reporterReplace or supplement the default output.