Skip to main content

React Verify and troubleshoot

Troubleshooting

Why does the build fail to download the manifest?

Check. vite build stops with an error from consentManifest that starts with the plugin's name, such as @c15t/core/build: could not fetch the consent manifest from <url> during the build. The name is @c15t/core/build for c15t/build and @c15t/vue/vite for c15t/vue/vite. vite dev logs the same message as a warning and keeps going. The part in parentheses names the cause:

MessageCauseFix
fetch failed, with ENOTFOUND, ECONNREFUSED or another network errorThe build cannot reach the backendBuild where the backend is reachable
no response within 10 secondsThe backend did not answer in timeCheck the backend, or build where it is reachable
/manifest responded 404The URL is not your project's backend, such as the https://your-project.inth.app placeholder or a URL missing its path prefixCopy the backend URL exactly as Inth shows it, or set VITE_C15T_BACKEND_URL
/manifest returned an invalid consent manifest.The URL answered with something other than a consent manifest, such as an HTML pagePoint backendURL at the backend itself, not your site or a dashboard page

A failed download never reuses an old snapshot. To deploy while the backend is down, run the build with C15T_ON_BUILD_ERROR=runtime. snapshot is then undefined, and the browser fetches the policy from the backend at runtime.

Only a build that uses manifest() downloads the manifest. The plugin fetches after Vite has dropped the code the app does not use, so a hosted() or offline() build never contacts the backend and never fails this way.

An error or warning that says no backend URL is set means the plugin found no backendURL option and no variable. Set VITE_C15T_BACKEND_URL (or VITE_INTH_PROJECT_URL) in .env or the build environment, or pass the absolute backend URL from your Inth project. The troubleshooting guide lists every message.

A notice that the build skipped the consent manifest fetch means the backend URL is relative, such as /api/c15t. Pass the absolute backend URL instead. With onBuildError: 'fail', this stops the build with build-time manifests require an absolute upstream URL.

Why is snapshot undefined?

Check. snapshot from c15t/generated is undefined, and the browser requests ${backendURL}/manifest on page load.

Fix. snapshot is undefined when Vite has no policy to serve:

  • consentManifest is missing from plugins in vite.config.ts. Without the plugin, c15t/generated still resolves, but exports undefined.
  • The fetch failed in vite dev, or in a build with C15T_ON_BUILD_ERROR=runtime. The terminal shows a could not fetch the consent manifest warning.
  • The backend URL is relative, so the plugin skipped the fetch.
  • consentManifest({ source: 'runtime' }) is set, so the plugin never fetches.

Fix the cause, then restart vite dev or rebuild. The plugin fetches once when Vite starts.

There is no banner

Check. In DevTools Network, find the GET request to /init on your backend URL. With the bundled manifest, the browser makes no request before the banner unless the policy depends on a country or region it does not know.

What you seeCauseFix
No request and no bannerConsentProvider is not mounted, an error stopped src/consent.tsx from rendering, or the bundled policy asks for no bannerCheck the console for errors, render one provider at the app root as in the quickstart, and inspect the active policy with the component below
/init fails with a CORS errorYour site's origin is not trustedAdd the exact origin, including port in development, to the trusted origins in your Inth project
/init returns 200The policy for this visitor asks for no banner, or a stored choice is still validInspect the active policy with the component below, or clear site data

A policy with prompt: 'none' shows no banner by design, and so does a returning visitor with a valid stored choice. Policies explains why a region may not show one.

The page throws that no IABProvider is mounted

The visitor's policy uses the iab model and the backend sent its vendor list, but no IABProvider is mounted. The standard banner and dialog do not handle the IAB model, so they throw rather than leave the visitor with no consent UI. Server and browser render the same error.

Fix it one of two ways:

  • If the site uses IAB TCF, render IABProvider with IABConsentBanner and IABConsentDialog from c15t/react/iab inside your consent provider. They can sit next to the standard banner.
  • If it does not, remove the iab model from the policy for that region in your Inth project or policy pack.

A backend that answers gvl: null turns IAB off for that request. The policy then runs as opt-in and nothing throws.

The check passes once c15t/react/iab has loaded. If you import the IAB components with a dynamic import(), a render of the standard banner that happens before the import finishes still throws.

Inspect the active policy

Render this component inside ConsentProvider. It reads state and records no choice:

src/consent-policy-debug.tsx
import { useSnapshot } from 'c15t/react';

export function ConsentPolicyDebug() {
	const snapshot = useSnapshot();
	const resolution = snapshot.resolution;

	return (
		<pre>
			{JSON.stringify(
				{
					status: resolution.status,
					policy: resolution.status === 'matched' ? resolution.policy.id : null,
					prompt: snapshot.promptRequirement,
					permissions: snapshot.effectivePermissions,
					location: snapshot.location,
					privacySignals: snapshot.privacySignals,
				},
				null,
				2
			)}
		</pre>
	);
}

The component re-renders on every consent change. Remove it after you finish. For a feature gate, use useConsent('marketing') or the category you need.

A vendor loads before the visitor chooses

Check. Search index.html, your entry module and any tag manager for the vendor's script or SDK initializer.

Fix. Remove every loader except the one in options.scripts. c15t only gates scripts registered with it, and the network blocker cannot stop code that runs before the provider renders. Scripts and embeds covers the options.

The banner has no styles

Check. In DevTools Elements, the banner has classes but no rules apply. Look for c15t's <style> element: <style data-href="c15t-first-paint"> in <head> on React 19, or <style data-c15t-styles> next to the banner on React 18 or with a nonce. Check the console for a Content Security Policy error.

Fix.

  • No c15t <style> element: the provider sets styles: false or noStyle. With styles: false, import c15t/react/styles.css once in the module that renders the provider.
  • The console reports a blocked inline style: set the provider's nonce, as Content Security Policy shows.
  • Under Tailwind 3, buttons lose their padding and cards their borders: Tailwind 3's unlayered preflight beats c15t's layered rules. Set styles: false, import c15t/react/styles.css above the @tailwind directives and add c15t/postcss-tailwind3 before tailwindcss in your PostCSS config. Without the plugin, Tailwind 3 drops c15t's rules or fails the build with `@layer components` is used but no matching `@tailwind components` directive is present. See Tailwind CSS.

The console warns that theme tokens are no longer turned into CSS

Check. options.theme holds colors, radius or other tokens, and the page has no c15t-theme style element. The provider only reads consentActions and slot styles from theme.

Fix. Move the tokens to your CSS or render ConsentTheme, as shown in customize.

The banner is missing from server-rendered HTML

Check. Your app uses React Router framework mode, Remix or another server-rendered setup without a c15t adapter. There this is expected: the browser resolves consent after hydration.

Fix. None is needed for consent to work. Rendering explains the options.

More help

Troubleshoot consent covers problems shared by every framework, such as a missing banner, analytics that load before a choice, imports that fail because npm installed c15t v2, choices that disappear on reload, server HTML that differs from the browser, static builds that fail and content blockers that hide the consent UI.