---
title: Troubleshooting
description: Fix a missing banner, early vendor requests, unstyled components
  and theme warnings in a React app that uses c15t/react.
group: frameworks
lastModified: "2026-10-10T16:01:45+01:00"
---
## 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:

|Message|Cause|Fix|
|--|--|--|
|`fetch failed`, with `ENOTFOUND`, `ECONNREFUSED` or another network error|The build cannot reach the backend|Build where the backend is reachable|
|`no response within 10 seconds`|The backend did not answer in time|Check the backend, or build where it is reachable|
|`/manifest responded 404`|The URL is not your project's backend, such as the `https://your-project.inth.app` placeholder or a URL missing its path prefix|Copy 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 page|Point `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](/docs/guides/troubleshooting#why-does-the-build-fail-to-download-the-manifest)
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 see|Cause|Fix|
|--|--|--|
|No request and no banner|`ConsentProvider` is not mounted, an error stopped `src/consent.tsx` from rendering, or the bundled policy asks for no banner|Check the console for errors, render one provider at the app root as in the [quickstart](/docs/frameworks/react/quickstart), and inspect the active policy with the component below|
|`/init` fails with a CORS error|Your site's origin is not trusted|Add the exact origin, including port in development, to the trusted origins in your Inth project|
|`/init` returns 200|The policy for this visitor asks for no banner, or a stored choice is still valid|Inspect 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](/docs/concepts/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:

```tsx title="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](/docs/frameworks/react/scripts)
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](/docs/frameworks/react/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](/docs/customization/tailwind#set-up-tailwind-css-3).

## 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](/docs/frameworks/react/customize#change-colors-fonts-and-radius).

## 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](/docs/frameworks/react/rendering#react-router-remix-and-other-server-rendered-apps)
explains the options.

## More help

[Troubleshoot consent](/docs/guides/troubleshooting) 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.
