Next.js Scripts and embeds
Network blocker
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
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.
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:
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 asnext/scriptwithbeforeInteractive), 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
fetchorXMLHttpRequestbefore the provider rendered. navigator.sendBeacon,WebSocket,EventSource, and requests made by<img>,<script>or<iframe>elements, web workers and service workers.- With the standalone
useNetworkBlockerhook 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
scriptsinstead of a<script>tag ornext/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.
- Before a choice, requests matching a rule are absent from the Network panel, and the console logs each blocked request.
- Allow the rule's category and save. Matching requests go out.
- Reject, reload, and check that they stay absent.