Skip to main content

Documentation

Documentation

The build fetches your public policy once and bundles it, so the server never fetches it at runtime.

Add the plugin before tanstackStart():

vite.config.ts
import { tanstackStart } from '@tanstack/react-start/plugin/vite';
import viteReact from '@vitejs/plugin-react';
import { consentManifest } from 'c15t/tanstack-start/build';
import { defineConfig } from 'vite';

export default defineConfig({
	plugins: [consentManifest(), tanstackStart(), viteReact()],
});

The plugin reads the backend URL from backendURL, or from VITE_C15T_BACKEND_URL, then VITE_INTH_PROJECT_URL, when you leave backendURL out. If VITE_C15T_BACKEND_URL is unset, the plugin sets it to the URL it used, so app code can read import.meta.env.VITE_C15T_BACKEND_URL instead of repeating the URL.

The plugin fetches the policy during vite build, or in vite dev when the server first loads it, and serves it to server code as snapshot from c15t/generated. In the browser bundle, snapshot is undefined, so the policy never ships to the browser. createConsentStateHandler() reads the snapshot and the backend URL from there, as the quickstart root route shows:

src/routes/__root.tsx
import { createServerFn } from '@tanstack/react-start';
import { createConsentStateHandler } from 'c15t/tanstack-start/server';

// Reads the backend URL and the policy consentManifest() downloaded.
const getConsentState = createServerFn({ method: 'GET' }).handler(
	createConsentStateHandler()
);

createConsentRoute() reads them the same way.

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:

CommandDefault when the fetch fails
Production build: next build, vite build, nuxt build, astro buildThe build stops with an error.
Dev: next dev, vite dev, nuxt dev, astro devA 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:

C15T_ON_BUILD_ERROR=runtime npm run build

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.

If the fetch fails, vite build stops. vite dev logs a warning instead, and snapshot is undefined, so the server fetches and caches the policy at runtime. To let a build continue the same way, set onBuildError: 'runtime', or run it with C15T_ON_BUILD_ERROR=runtime:

vite.config.ts
consentManifest({ onBuildError: 'runtime' }),

For policy updates without a rebuild, pass mode: manifest({ source: 'runtime' }) to createConsentStateHandler and keep the plugin, which still supplies the backend URL. See runtime fetching.