React Verify and troubleshoot
Troubleshooting
There is no banner
Check. In DevTools Network, find the GET request to /init on your
backend URL. With hosted(), the browser sends it while the provider first
renders. With a 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, 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
explains why a region may not show one.
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
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:
consentManifestis missing frompluginsinvite.config.ts. Without the plugin,c15t/generatedstill resolves, but exportsundefined.- The fetch failed in
vite dev, or in a build withC15T_ON_BUILD_ERROR=runtime. The terminal shows acould not fetch the consent manifestwarning. - 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.
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
IABProviderwithIABConsentBannerandIABConsentDialogfromc15t/react/iabinside your consent provider. They can sit next to the standard banner. - If it does not, remove the
iabmodel 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:
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 setsstyles: falseornoStyle. Withstyles: false, importc15t/react/styles.cssonce 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, importc15t/react/styles.cssabove the@tailwinddirectives and addc15t/postcss-tailwind3beforetailwindcssin 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.