---
title: Network blocker
description: Hold fetch and XMLHttpRequest calls in a Next.js app until their
  consent category is allowed, with networkBlocker rules in c15t.config.ts.
group: frameworks
lastModified: "2026-10-10T16:01:45+01:00"
---
## Add rules

The network blocker stops `fetch` and `XMLHttpRequest` calls to domains you
list until the visitor grants their category. Use it for beacons and API calls
that bypass script loading, such as a pixel fired by an SDK already on the
page. Load vendor SDKs through [`scripts`](/docs/frameworks/next/scripts)
first, so they do not run at all before consent.

Define the rules in `c15t.config.ts`. `onRequestBlocked` is a function, so the
rules cannot be passed as a prop from a Server Component, but the config is
bundled into the browser and can hold them.

```ts title="c15t.config.ts"
import { defineConsentConfig } from 'c15t/next';

export default defineConsentConfig({
	networkBlocker: {
		rules: [
			{
				id: 'google-analytics',
				domain: 'google-analytics.com',
				category: 'measurement',
			},
			{
				id: 'meta-pixel',
				domain: 'facebook.com',
				pathIncludes: '/tr',
				category: 'marketing',
			},
		],
	},
});
```

Keep your `scripts` in the same call. Keep the page content inside
`ConsentRoot`. Blocking starts when it renders, so
components outside it that send requests while rendering, and modules evaluated
before it, are not covered. Requests your server makes, in Server Components,
route handlers or `getServerSideProps`, are not blocked either.

## Match requests with rules

Each rule names a `domain` and the consent `category` a request needs. The
domain also matches its subdomains: `google-analytics.com` covers
`www.google-analytics.com`. `pathIncludes` narrows the rule to paths that
contain a substring, and `methods` narrows it to HTTP methods. A request is
blocked when a matching rule's condition is not met by the visitor's
effective permissions.

`category` takes the same conditions as scripts:

```ts
{ category: 'measurement' }
{ category: { and: ['measurement', 'marketing'] } }
{ category: { or: ['measurement', 'marketing'] } }
```

Add `vendor` to also block the request while the visitor has turned that
vendor off. IAB TCF rules use `vendorId` and the `iab*` purpose fields
instead.

|Option|Default|Purpose|
|--|--|--|
|`rules`|required|Rules described above|
|`enabled`|`true`|Set `false` to keep the rules but stop blocking|
|`logBlockedRequests`|`true`|Log each blocked request with `console.warn`|
|`onRequestBlocked`|none|Called with `{ method, url, rule }` for each blocked request|

## What a blocked request looks like

The blocker wraps `window.fetch` and `XMLHttpRequest`. A blocked `fetch`
resolves to a `451` response with the status text
`Request blocked by consent`, and nothing is sent. A blocked XHR is aborted
and fires an `error` event. Requests that match no rule are not delayed.

## When blocking starts

The provider holds matching requests from its first render in the browser,
before any of its children render or run effects. That covers requests from
child components, including their mount effects, and from effects in
components rendered next to the provider. The blocker module itself loads
after mount and decides each held request. Apps without `networkBlocker` do
not download it.

The standalone `useNetworkBlocker` hook works the same way from the first
render of the component that calls it. That render patches `fetch` and
`XMLHttpRequest`. If React throws the render away and never commits it, the
hold ends after 10 seconds. Nothing checked consent for the requests it held,
so they fail the way the blocker fails a blocked request: a 451 response for
`fetch`, a failed XHR. The same happens when the component unmounts before
the blocker loads.

While consent is unknown, a matching request that would be blocked waits
instead of failing. Consent is unknown until the policy has loaded, which is
also when a returning visitor's stored choice takes effect. The request is
then sent if the choice allows it and blocked otherwise. If the policy fails
to load, optional categories stay denied and waiting requests are blocked.
If the policy request never finishes, they keep waiting and are never sent.

A synchronous XHR cannot wait. Before the blocker module has loaded, a
matching one throws a `NetworkError` from `send()`. After that, one that
consent does not allow yet is blocked.

## What the network blocker cannot stop

The blocker only sees requests made after the provider starts rendering in
the browser, through `fetch` or `XMLHttpRequest`. It cannot stop:

* Code that runs before the provider renders: inline scripts in the HTML,
  third-party tags in `<head>`, scripts loaded before hydration (such as
  `next/script` with `beforeInteractive`), and client modules that evaluate
  earlier. Webpack builds evaluate a route's client component modules when
  its chunk loads, so their top-level code runs first. Turbopack evaluates a
  client component module when its first element renders, which inside the
  provider is after blocking starts.
* Code that saved its own reference to `fetch` or `XMLHttpRequest` before the
  provider rendered.
* `navigator.sendBeacon`, `WebSocket`, `EventSource`, and requests made by
  `<img>`, `<script>` or `<iframe>` elements, web workers and service
  workers.
* With the standalone `useNetworkBlocker` hook instead of the provider
  option, requests sent before the component that calls it renders.
  Blocking starts in that component's first render, not the provider's.

Keep tracking calls out of that window:

* Send them from an effect or an event handler, never at module top level.
* Load vendor SDKs through `scripts` instead of a `<script>` tag or
  `next/script`, so they wait for consent before they run at all.
* Check `useConsent('measurement')` (or the category you need) before you
  call a vendor from your own code, and treat the blocker as a backstop.

## Verify the blocked requests

Open the production build in a private window with the DevTools Network panel
open, under a policy that asks for consent.

1. Before a choice, requests matching a rule are absent from the Network panel,
   and the console logs each blocked request.
2. Allow the rule's category and save. Matching requests go out.
3. Reject, reload, and check that they stay absent.
