Skip to main content

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.

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:

{ 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.

OptionDefaultPurpose
rulesrequiredRules described above
enabledtrueSet false to keep the rules but stop blocking
logBlockedRequeststrueLog each blocked request with console.warn
onRequestBlockednoneCalled 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.