---
title: Troubleshooting
description: Fix common c15t Vue plugin problems, from unresolved imports and a
  missing banner to tokens that do not apply and scripts that load twice.
group: frameworks
lastModified: "2026-10-10T16:01:45+01:00"
---
## Vite fails to pre-bundle c15t's .vue files

**Check.** `vite dev` reports an error from dependency optimization for a
`.vue` file under `@c15t/vue`, or `manifest()` throws `c15t: manifest() has no backend URL`. `consentManifest()` is missing from the `plugins` array of
`vite.config.ts`.

**Fix.** Add `consentManifest()` from `c15t/vue/vite` to the `plugins` array,
next to `@vitejs/plugin-vue`, and set `VITE_C15T_BACKEND_URL` (or
`VITE_INTH_PROJECT_URL`). See the
[quickstart](/docs/frameworks/vue/quickstart#add-the-vite-plugin).

## app.use() throws "pass `mode`"

**Check.** The plugin options have no `mode`. Earlier v3 alphas took
`backendURL`, `manifest`, `manifestURL` and `manifestSnapshot` on the plugin.

**Fix.** Pass `mode: manifest()`, `hosted()` or `offline()` from
`c15t/vue/vue-plugin`. Move `backendURL` into the mode, such as
`hosted({ backendURL })`, or let `consentManifest()` supply it.

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

## app.use() fails or does nothing

**Check.** Look at where you import the plugin. `c15t/vue` is the Nuxt module
and is not a Vue plugin.

**Fix.** Import the plugin from `c15t/vue/vue-plugin`.

## A composable throws "Kernel not found"

**Check.** The component is outside the app that installed `c15tVue`, for
example in a separate `createApp()` instance, or the composable ran outside
`setup`.

**Fix.** Install the plugin on the app that renders the component, and call
composables inside `setup`.

## Why is there no banner?

**Check.** [DevTools](/docs/frameworks/vue/components/dev-tools) shows the
matched policy. With `hosted()`, find the `/init` request in the Network tab;
with `manifest({ source: 'runtime' })`, the `/manifest` request.

**Fix.** Work through these causes in order:

1. **The visitor's policy asks for no banner.** A policy with prompt `none`
   shows no banner, which is common in opt-out regions. See
   [why the banner may be absent](/docs/concepts/policies#why-the-banner-may-be-absent).
2. **`manifest()` resolves the unknown-location policy.** Without `inputs`
   or `geoURL`, every visitor gets the policy your project assigns when the
   location is unknown. Check that rule, then rebuild.
3. **`ConsentRoot` is inside a route that unmounts.** Put it in the root
   component.
4. **The backend did not answer.** With `hosted()`, `/init` must return
   200; with a runtime manifest, `/manifest` must. A CORS error means your site's origin is missing from the
   Inth project's trusted origins. While resolution fails, c15t shows no UI and
   keeps every optional category denied.
5. **A stored choice exists.** Clear the `c15t` cookie and localStorage entry,
   or use a private window.

## The app throws IABUnavailableError

**Check.** The error has the code `C15T_IAB_UNAVAILABLE` and reads
"c15t: this visitor's policy uses IAB TCF, but `iab` is not set." The
visitor's policy uses the `iab` model and the backend sent its vendor list,
but the plugin has no `iab` option, or has `iab: false` or
`iab: { enabled: false }`. The standard banner does not handle the IAB model,
so c15t throws rather than leave the visitor with no consent UI.
`app.use()` throws when the policy arrives with `prefetch`; otherwise the
error is uncaught once the policy resolves, and `ConsentRoot` renders
nothing.

**Fix.** If the site uses IAB TCF, pass `iab` to the plugin, such as
`app.use(c15tVue, { iab: {} })`. See [IAB TCF](/docs/frameworks/vue/iab).
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, and nothing throws.

## Tokens in my CSS do not apply

**Check.** The c15tVue plugin appends a `<style id="c15t-css-vars">` element to
`<head>` when you install it. The element sets every token, including the
defaults. A `:root` rule in your stylesheet for the same variable competes
with it on source order, so it may lose.

**Fix.** Set values through the plugin's `tokens` option. See
[customize](/docs/frameworks/vue/customize#set-theme-tokens).

## A script loads twice, or before consent

**Check.** Look for the vendor in both the plugin's `scripts` and a
`createScriptLoader` call.

**Fix.** Keep only `scripts`.

**Check.** Look for the vendor's own snippet in `index.html`, or SDK
initialization in your code.

**Fix.** Remove it so c15t is the only loader.

## The page flashes without the banner on a server-rendered Vue app

**Check.** c15t does not support server rendering for plain Vue.

**Fix.** Use the c15t Nuxt module. See
[server rendering without Nuxt](/docs/frameworks/vue/rendering#server-rendering-without-nuxt).

## Names from an earlier alpha are missing

**Check.** An import or component from an earlier v3 alpha fails to resolve,
such as `c15t/vue/consent-root`, `ConsentPreferencesLink` or the default
export of `c15t/vue/vite`. The v3 alphas removed names that never shipped in
a stable release, with no alias.

**Fix.** Use the current names:

|Removed|Use instead|
|--|--|
|Vue plugin options `backendURL`, `manifest`, `manifestSnapshot`, `manifestURL`, `customFetch`, `domain`|`mode`, a factory from `c15t/vue/vue-plugin` that takes them|
|The default `c15tVue` export of `c15t/vue/vite`|`consentManifest` from `c15t/vue/vite`. The Vue app plugin keeps the name `c15tVue`.|
|`c15t/vue/consent-root`, `c15t/vue/consent-widget`|Named exports from `c15t/vue/vue-plugin`|
|`ConsentPreferencesLink`, `runtime/components/consent-preferences-link.vue`|`ConsentDialogLink`, `consent-dialog-link.vue`|
|`ConsentFrame`|`ConsentGate`|
|`hosted({ url })`|`hosted({ backendURL })`, or `VITE_C15T_BACKEND_URL`|

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