net::ERR_BLOCKED_BY_CLIENT and net::ERR_ABORTED (tested)
net::ERR_BLOCKED_BY_CLIENT means something inside the browser, almost always an ad or privacy blocker extension, cancelled the request before it left the machine. Your server never sees it. net::ERR_ABORTED means the browser itself cancelled a request, usually on purpose. We reproduced both in Chromium 153 and logged exactly what each layer reports.
1. What the two errors mean
ERR_BLOCKED_BY_CLIENT: the "client" is the browser plus its extensions. An extension rule matched the URL, and Chromium refused to send the request. There is no HTTP status, because there was no HTTP exchange.
ERR_ABORTED: the request started, and then the browser dropped it. Common reasons: a new navigation replaced it, the response was a download, the response was a 204, or your code called abort(). Most ERR_ABORTED entries are harmless.
2. Our test setup
We wrote a minimal Manifest V3 extension with two declarativeNetRequest block rules: urlFilter: "/ads.js" and urlFilter: "/analytics". We loaded it unpacked with Playwright's launchPersistentContext and --load-extension.
{"id":1,"priority":1,"action":{"type":"block"},
"condition":{"urlFilter":"/ads.js",
"resourceTypes":["script","xmlhttprequest","image","ping","main_frame"]}}
A note on headless: with Playwright's default headless shell, the extension did not load. /ads.js ran normally. With channel: 'chromium' (the new headless mode) the extension loaded and blocked as expected. All extension results below use that mode.
The test page loaded <script src="/ads.js"> and an <img src="/analytics/pixel.gif">, then ran fetch, XHR and sendBeacon against /analytics/....
3. Results: what each layer sees when an extension blocks a request
| Request | What your code sees | Console | DevTools protocol (Network panel) |
|---|---|---|---|
<script src="/ads.js"> | onerror fires (event type error). Script does not run. | Failed to load resource: net::ERR_BLOCKED_BY_CLIENT | Script, errorText net::ERR_BLOCKED_BY_CLIENT, blockedReason "other" |
<img src="/analytics/pixel.gif"> | onerror fires | same message | Image, ERR_BLOCKED_BY_CLIENT |
fetch('/analytics/collect') | TypeError: Failed to fetch | same message | Fetch, ERR_BLOCKED_BY_CLIENT |
XHR GET /analytics/x | onerror, status 0, readyState 4 | same message | XHR, ERR_BLOCKED_BY_CLIENT |
navigator.sendBeacon('/analytics/b') | Returned true, even though it was blocked | same message | Ping, ERR_BLOCKED_BY_CLIENT |
Top-level navigation to /analytics/page | Error page: "127.0.0.1 is blocked. This page has been blocked by an extension. Try disabling your extensions. ERR_BLOCKED_BY_CLIENT" | - | Document, ERR_BLOCKED_BY_CLIENT; Playwright page.goto: net::ERR_BLOCKED_BY_CLIENT |
Our server log had zero hits for /ads.js or /analytics during this run. The block happens before any network traffic. Server logs, curl and uptime checks will all look fine.
The fetch error is the same generic TypeError: Failed to fetch you get for CORS failures and refused connections. JavaScript cannot tell them apart. See TypeError: Failed to fetch for the full list.
Also note sendBeacon: it returning true only means the beacon was queued. It does not mean it was delivered.
4. Testing without an extension
Playwright can fake the same error with route.abort('blockedbyclient'). In our run, script and image onerror fired and fetch threw TypeError: Failed to fetch, just like the real extension. One visible difference: the console said net::ERR_BLOCKED_BY_CLIENT.Inspector. That suffix marks a block made by DevTools, not by an extension.
await page.route('**/ads.js', r => r.abort('blockedbyclient'));
5. Which URLs get blocked (from the docs, not measured)
Real blockers use filter lists such as EasyList and EasyPrivacy. Per the EasyList and uBlock Origin docs, they match on URL substrings and domains, not on what the file does. Patterns that often match:
- Paths with
/ads/,/ad/,/adv/,ads.js,banner,sponsor - Tracker names like
analytics.js,gtag,/collect,pixel,track,beacon - Whole third-party domains of ad and analytics vendors
So an innocent file named /static/ads.js or an API route /api/analytics/ can break your own site for blocker users.
6. How to fix ERR_BLOCKED_BY_CLIENT
- If it is your own code: rename files and routes so they do not look like ads or tracking.
/api/analyticsbecomes/api/stats;ads.jsbecomespromo-slots.js. Do this for honest naming, not to sneak trackers past users. - Fail soft: treat blocked third-party scripts as optional. Wrap calls like
window.gtag?.(...)and catch fetch errors, so a missing analytics script never breaks checkout or login. - Confirm the cause: open the page in a private window with extensions off, or another browser. If the error disappears, it was an extension. The URL returns fine with curl.
- Respect the user: if the blocked request is a tracker, the user chose to block it. Do not try to bypass it.
7. net::ERR_ABORTED: five cases we measured
Same Chromium 153, no extension. A /slow route answered after 3 s.
| Case | What happened | Network / Playwright | Console |
|---|---|---|---|
Navigation interrupted by another navigation (location.href set again after 300 ms, or a second page.goto) | New page loads; first request cancelled | Document, net::ERR_ABORTED, canceled=true; first page.goto rejects with net::ERR_ABORTED at .../slow | Nothing |
Navigating to a response with Content-Disposition: attachment | Response 200 arrived, then became a download; page stayed on old URL | Document 200, then net::ERR_ABORTED; page.goto throws "Download is starting" | Nothing |
| Navigating to a 204 No Content | Response 204 arrived; page stayed on old URL | Document 204, then net::ERR_ABORTED; page.goto throws net::ERR_ABORTED | Nothing |
fetch + AbortController.abort() at 300 ms | AbortError: signal is aborted without reason | Fetch, net::ERR_ABORTED, canceled=true | Nothing |
XMLHttpRequest.abort() at 300 ms | onabort fires, status 0 | XHR, net::ERR_ABORTED, canceled=true | Nothing |
Two more fetch variants: AbortSignal.timeout(300) threw TimeoutError: signal timed out. abort('user left') rejected with the plain string "user left", not an Error object. Check signal.aborted rather than err.name if you pass a custom reason.
Unlike ERR_BLOCKED_BY_CLIENT, none of the ERR_ABORTED cases wrote a console error. You mostly see them in the Network panel, in test runners like Playwright, and in error monitoring tools.
On the server side, an aborted request did arrive, and the client just left early. nginx logs that as 499 Client Closed Request.
8. How to fix ERR_ABORTED
- Downloads: expected. In Playwright, use
page.waitForEvent('download')and click the link instead ofpage.goto. See Content-Disposition filename. - 204 on navigation: expected. Do not navigate to endpoints that return 204. Call them with
fetch, or return 200 / a redirect for form posts. - Double navigation: look for code that sets
locationtwice, a form submit plus a JS redirect, or a link with bothhrefand anonclickthat navigates. - Your own aborts: filter them out of error reporting:
if (signal.aborted) return; - Tests: wait for the navigation you actually want (
waitForURL) before starting the next one.
For CORS messages that also end in "Failed to fetch", see CORS error messages explained.
Frequently asked questions
Does ERR_BLOCKED_BY_CLIENT mean my server is down?
No. The request never left the browser. In our test the server logged zero hits for the blocked URLs, and the same URLs returned 200 with curl. An extension or browser policy blocked it.
Can JavaScript detect that a request was blocked by an extension?
Not exactly. fetch throws the generic TypeError: Failed to fetch, XHR gets status 0, and script or img onerror fires. These look the same as network or CORS errors. Only DevTools shows ERR_BLOCKED_BY_CLIENT.
Why is my own /api/analytics endpoint blocked?
Filter lists match URL substrings like /analytics, /ads/ and pixel (per the EasyList docs). Our test rule urlFilter /analytics blocked fetch, XHR, img, sendBeacon and navigation. Rename the route to something neutral.
Is net::ERR_ABORTED an error I need to fix?
Usually not. In our tests it came from a second navigation, a download, a 204 response, or an explicit abort(). None of them wrote a console error. Fix it only if a request you needed is being cancelled.
Why does Playwright page.goto throw net::ERR_ABORTED on a 204?
A 204 has no content to show, so Chromium cancels the navigation and stays on the old page. We measured Document 204 followed by net::ERR_ABORTED. Use page.request or fetch for that endpoint instead.
Written with AI assistance. Measured on 2026-10-02 with Chromium 153.0.8010.12 driven by Playwright 1.63 (new headless mode via channel 'chromium'), a test Manifest V3 extension with declarativeNetRequest block rules, and a Node.js 24.21.0 server. Which URLs real blocklists match is from the EasyList and uBlock Origin docs, not measured. PlainHeaders is our extension; Chromium, Playwright and Node.js are free.