Vue
Rendering and deployment
Pick the setup for your deployment
The Vue plugin runs in the browser only. The mode option of
app.use(c15tVue, { mode }) decides where each visitor's policy comes from.
Import the mode from c15t/vue/vue-plugin:
| Your app | mode |
|---|---|
| Vue with Vite, served as static files | manifest(), as in the quickstart. The build bundles the policy and the browser resolves it. Plan location inputs or an unknown-location rule. |
| Policy edits must apply without a rebuild | manifest({ source: 'runtime' }) |
| Regional policies that need the visitor's location from the backend | hosted() |
| A prototype with no backend | offline(). Not recommended for production environments. |
| Vue rendered on the server without Nuxt | Any of the above; the banner mounts after hydration. For the banner in server HTML, use Nuxt. |
mode is required. Without it, app.use() throws and asks for one. Each
mode is imported statically, so the bundle carries only the modes your app
imports.
Bundle the manifest during builds
The quickstart and examples/vue in the
c15t repository use this setup.
The build fetches your public policy once and bundles it, so the server never fetches it at runtime.
consentManifest() from c15t/vue/vite downloads the policy during
vite build, when the app uses manifest(), and in vite dev when the app
first loads it:
Without a backendURL option, the plugin reads VITE_C15T_BACKEND_URL, then
VITE_INTH_PROJECT_URL, from the environment or .env. manifest() from c15t/vue/vue-plugin, with no
options, reads the downloaded policy and the same backend URL, so src/main.ts
needs no import from c15t/generated:
The browser resolves the visitor's policy from the bundled snapshot, with no
/manifest request. When the policy depends on the visitor's country or
region and neither inputs nor geoURL supplies one, the browser asks the
backend's /init instead. The app bundles the policy and English copy, and
loads the resolver as its own chunk when it needs it.
When the build has no snapshot, as in vite dev after a failed download,
manifest() fetches ${backendURL}/manifest when the app starts.
The snapshot is fixed at build time:
- Rebuild after changing policies, translations or vendors. If your CI caches build output, force a fresh build.
- Consent choices still go to the backend.
The build reads the backend URL from your public backend URL variable when
the config doesn't pass one: NEXT_PUBLIC_C15T_BACKEND_URL in Next.js,
NUXT_PUBLIC_C15T_BACKEND_URL in Nuxt, PUBLIC_C15T_BACKEND_URL in Astro,
Svelte and SvelteKit, and VITE_C15T_BACKEND_URL in TanStack Start and other
Vite apps. Each also reads the matching Inth variable, such as
NEXT_PUBLIC_INTH_PROJECT_URL, when the c15t one is unset. See
set the backend URL.
The fetch waits at most 10 seconds. When it fails, or no backend URL is set, every framework does the same thing:
| Command | Default when the fetch fails |
|---|---|
Production build: next build, vite build, nuxt build, astro build | The build stops with an error. |
Dev: next dev, vite dev, nuxt dev, astro dev | A warning, and the server fetches the policy at runtime. |
Set onBuildError to use one behaviour for both. 'fail' stops dev too.
'runtime' lets a production build finish, and the server fetches the policy
at runtime. The C15T_ON_BUILD_ERROR environment variable overrides the
option, so you can deploy during a backend outage without a code change:
Turborepo's strict environment mode hides undeclared variables from tasks, so
list C15T_ON_BUILD_ERROR in the build task's passThroughEnv there.
The build skips the fetch, without an error, when it can't use a snapshot,
for example when the backend URL is relative. With onBuildError: 'fail', a
relative URL stops the build. Consent modes
lists every case.
vite build and vite dev fetch the manifest when they start. vite preview
serves the last build without fetching. The plugin writes no file into your
app, so there is nothing to keep out of Git. c15t/generated ships its own
types, so tsc, vue-tsc and svelte-check pass on a fresh checkout without
a build first.
The plugin can't see the options your app passes to manifest(). When the
app passes source: 'runtime' or manifestURL, set
consentManifest({ source: 'runtime' }) too. The build then downloads no
manifest, so it doesn't fail when the backend's /manifest is down.
For policy edits without a rebuild, use
manifest({ source: 'runtime' }),
or hosted()
when you need the backend's geolocation.
Tell the browser where the visitor is
The browser has no request location. When the policy depends on the
visitor's country or region, manifest() asks the backend's /init on the
first visit, as React and Svelte do, rather than apply the rule for an
unknown location, which could be another region's. vite build warns when
it bundles such a policy and suggests hosted(), because the bundled policy
then saves no request. To resolve in the browser instead:
- Pass
countryandregionprops toConsentRootwhen the page already knows the location, for example from a value your edge server injected. - Pass
inputs: { country, region }tomanifest()for the same purpose, set once when the plugin installs. - Set
geoURLto a same-origin route you run that returns the visitor's{ country, region }as JSON.manifest()requests it only when the policy depends on a location it does not have. - Pass
initFallback: falsetomanifest()to resolve the policy for an unknown location without asking the backend. Only do this when that rule is right for every visitor it reaches.
Load the visitor's language
The bundle includes English copy. When a visitor resolves to another language your project has text for, the browser loads the base text for that language from your site the first time it is needed. A visitor downloads their own language only, never the full set.
Fetch the manifest at runtime
manifest({ source: 'runtime' }) ignores the build's snapshot and fetches
${backendURL}/manifest when the app starts. The browser resolves the
policy from it, so policy edits apply without a rebuild:
Keep consentManifest() in vite.config.ts: manifest() reads the backend
URL from it. The plugin can't see options you pass in app code, so set
consentManifest({ source: 'runtime' }) as well. Without it, the build still
downloads the manifest, and fails when the backend is down.
To fetch the manifest from somewhere else, such as a CDN in front of your
backend, pass manifest({ manifestURL }). The app fetches that URL when it
starts and ignores the build's snapshot. As with source: 'runtime', set
consentManifest({ source: 'runtime' }) so the build doesn't download the
manifest. Consent saves still go to the backend URL from consentManifest().
The manifest is the same for every visitor, so a CDN can cache it. Location,
geoURL and language loading work as in the
build-time setup. Consent saves
still go to the backend.
Ask the backend for each visitor's policy
hosted() sends each visitor's first page load to the backend's /init.
The backend reads the visitor's location from the request, resolves the
policy and returns the text in the visitor's language:
hosted() reads the backend URL from consentManifest(), or takes it as
hosted({ backendURL: 'https://your-project.inth.app' }). Keep
consentManifest() in vite.config.ts either way: it also keeps the c15t
components, which are .vue files, out of Vite's dependency pre-bundling.
A hosted() build never downloads the manifest, so a backend outage does
not stop it.
Until /init answers, every optional category is denied, so gated scripts
and iframes wait. The build output is plain static files. Deploy it to any
static host. The backend must allow your site's origin. With Inth, add it to
the project's trusted origins.
Data fetching compares the two paths.
Resolve the policy without a backend
offline() resolves policy rules in the browser and stores choices in the
visitor's cookie and localStorage. There is no backend, so no consent
records are kept. Not recommended for production environments.
Without options, offline() uses c15t's recommended policy rules. Pass
offline({ policyRules }) to replace them.
Server rendering without Nuxt
c15t has no server helper for plain Vue. The plugin accepts prefetch and
initialRecords, but nothing produces them for a custom Vue server. A
server-rendered page renders without the banner, and the banner appears after
hydration.
Your theme tokens can still be in the first HTML. generateTokensCSS() from
c15t/vue/vue-plugin returns the CSS the plugin applies. Put it in a
<style id="c15t-css-vars"> element in your server HTML, and the plugin
reuses that element in the browser:
For the banner in server-rendered HTML, use the c15t Nuxt module. Start with the Nuxt quickstart in the framework list. It resolves the policy on the server, renders the banner and tokens in the first HTML, and hydrates without another request.
Verify your deployment
Serve the production build and open the Network tab:
mode | Policy requests on the first page load |
|---|---|
manifest() with a build snapshot | None to the backend. A language other than English loads its text from your site. |
manifest({ source: 'runtime' }) | One GET to ${backendURL}/manifest |
manifest({ manifestURL }) | One GET to manifestURL |
hosted() | One GET to ${backendURL}/init |
offline() | None |
Reject, reload, and confirm the banner stays closed. Then follow verify consent for the vendor checks.