---
title: Choose your setup
description: Pick the c15t setup for your framework, rendering mode and hosting,
  and decide who runs the consent backend.
group: concepts
lastModified: "2026-10-10T16:01:45+01:00"
---
## Find your app in this table

Most apps need one row. Follow its guide from start to finish; each guide links
to the alternatives it rules out.

|Your app|Setup|Guide|
|--|--|--|
|Next.js App Router with a Next.js server|Server resolves consent from a build-time manifest and streams it to the page|[App Router](/docs/frameworks/next/quickstart)|
|Next.js App Router, page must arrive with its banner|Await consent inside `Suspense`|[Stream or await](/docs/frameworks/next/rendering#stream-or-await-consent-in-the-app-router)|
|Next.js with static, ISR, `'use cache'` or `ensureStatic = 'navigation'` pages|Browser resolves consent on those pages|[Static, ISR and cached pages](/docs/frameworks/next/rendering#render-static-isr-and-cached-pages)|
|Next.js Pages Router|Resolve consent in `getServerSideProps`|[Pages Router](/docs/frameworks/next/rendering#pick-your-rendering-path)|
|Next.js with `output: 'export'`|Browser resolves consent|[Static export](/docs/frameworks/next/static-export)|
|TanStack Start with a server|Root loader resolves consent on the server|[TanStack Start](/docs/frameworks/tanstack-start/quickstart)|
|TanStack Start on a serverless host, banner may follow the page|Root loader streams consent|[TanStack Start rendering](/docs/frameworks/tanstack-start/rendering#stream-the-page-while-consent-resolves)|
|TanStack Start in SPA mode or prerendered|Browser resolves consent|[TanStack Start rendering](/docs/frameworks/tanstack-start/rendering#spa-mode-prerendered-pages-and-static-hosts)|
|React with Vite, or another single-page app|Browser resolves consent|[React](/docs/frameworks/react/quickstart)|
|React Router framework mode, Remix, or another server-rendered React app|Browser resolves consent after hydration|[React rendering](/docs/frameworks/react/rendering#react-router-remix-and-other-server-rendered-apps)|
|Nuxt with a Nuxt server|Nuxt module resolves consent on the server from a build-time manifest|[Nuxt](/docs/frameworks/nuxt/quickstart)|
|Nuxt with prerendered or cached routes|Nuxt server for most pages, the browser on prerendered and cached routes|[Nuxt prerendered and cached routes](/docs/frameworks/nuxt/rendering#prerendered-and-cached-routes)|
|Nuxt with `nuxt generate` on static hosting|Nuxt module, browser resolves consent from the manifest or `/init`|[Nuxt static hosting](/docs/frameworks/nuxt/rendering#deploy-to-static-hosting)|
|Nuxt with `ssr: false`|Nuxt module, browser resolves consent from the manifest|[Nuxt single-page app](/docs/frameworks/nuxt/rendering#build-a-single-page-app-with-ssr-false)|
|Vue with Vite|Vue plugin, browser resolves consent|[Vue](/docs/frameworks/vue/quickstart)|
|Vue rendered on the server without Nuxt|Vue plugin; the banner mounts after hydration. Use Nuxt for the banner in server HTML.|[Vue rendering](/docs/frameworks/vue/rendering#server-rendering-without-nuxt)|
|Astro with static output|Browser resolves consent through the hosted backend|[Astro static output](/docs/frameworks/astro/rendering#static-output-with-hosted)|
|Astro with server output and an adapter|Middleware resolves consent from a build-time manifest|[Astro server output](/docs/frameworks/astro/rendering#server-output-with-manifest)|
|Svelte with Vite|Svelte provider, browser resolves consent|[Svelte](/docs/frameworks/svelte/quickstart)|
|SvelteKit with a server|Root layout load resolves consent on the server|[SvelteKit](/docs/frameworks/sveltekit/quickstart)|
|SvelteKit with `adapter-static`, prerendered pages or SPA mode|Browser resolves consent|[SvelteKit rendering](/docs/frameworks/sveltekit/rendering#build-a-static-site)|
|Plain HTML, a CMS, a page builder, or a static site generator such as Hugo or Eleventy|One script tag, stock banner included|[HTML](/docs/frameworks/html/quickstart)|
|JavaScript with a bundler and no UI framework|`@c15t/browser` module, stock banner included|[JavaScript](/docs/frameworks/javascript/quickstart)|
|Your own UI, or a framework without an adapter such as Solid|Headless runtime|[JavaScript headless](/docs/frameworks/javascript/headless)|

Solid has no c15t adapter. Use the [headless runtime](/docs/frameworks/javascript/headless#use-c15t-with-solid)
and subscribe to it from your components.

## Decide who runs the consent backend

The backend supplies policies and stores consent records. Every setup above
works with each option; only the URL changes.

|Option|Use it when|
|--|--|
|[Inth](https://inth.com)|You want hosted policies and consent records. The guides use this.|
|[Self-hosted c15t backend](/docs/self-host/overview)|You need to run the service and its database yourself.|
|Offline mode|Local development and tests. Policies live in your code and choices stay in the browser, with no consent records. Not recommended for production environments.|

To use Inth, create a project, set its policy rules, add your site's origin to
its trusted origins, and copy the backend URL. The guides use the placeholder
`https://your-project.inth.app`; replace it with your project's backend URL,
including any path prefix.

## Server rendering or browser rendering

Where consent resolves decides what the visitor's first HTML contains.

|Rendering|What the visitor gets|
|--|--|
|The server resolves consent for each request|The server reads the visitor's cookie and location headers, so the browser starts with the answer. Gated scripts can run right after hydration, and the banner can be part of the HTML.|
|The browser resolves consent|The HTML is the same for everyone. After the page loads, the browser requests the policy, then shows the banner or runs gated scripts.|

A static or prerendered page cannot contain one visitor's consent, because the
same HTML goes to everyone. Static sites still use Inth; the browser talks to it
directly. A same-origin `/api/c15t` route only works where a server runs it.

Pages that are cached and shared between visitors, such as Next.js ISR or
`'use cache'` routes, need browser resolution for the same reason. Cache the
public policy manifest, never a response that contains a visitor's consent.

## How the server gets the policy

Use a build-time manifest as the default for supported production builds.
`manifest()` is the default mode in Next.js, TanStack Start, Nuxt, Astro and
SvelteKit, so their servers resolve requests from the deployment's snapshot.
Vite apps can bundle it for browser resolution too; location-based policies
may still need a location request. The framework quickstarts show the build
integration. [Consent modes](/docs/concepts/modes) lists every mode, its
options and the backend URL variable each framework reads.

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

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](/docs/concepts/modes#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:

```sh
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](/docs/concepts/modes#what-happens-when-the-download-fails)
lists every case.

For deployments that need policy updates without a rebuild, choose a runtime
path:

* **Cached manifest**, `manifest({ source: 'runtime' })`.
  Your server downloads the project's public policy file, caches it, and
  resolves each visitor locally. Consent saves still go to the backend.
* **`/init`**, `hosted()`. Each visit asks the backend to resolve the visitor.
  Simpler, but every page load makes a request to the backend.

[Data fetching](/docs/concepts/data-fetching) compares them in detail,
including proxies and custom transports.
