---
title: Optimization
description: Improve c15t startup performance in Next.js with same-origin rewrites, static prefetching, and dynamic-route SSR.
lastModified: 2026-04-14
---
Use this guide when you care about banner visibility speed, route static-ness, and reducing backend round-trip cost.

## Start Here

Apply the optimizations in this order:

| Situation                                           | Use                             | Why                                                                               |
| --------------------------------------------------- | ------------------------------- | --------------------------------------------------------------------------------- |
| Any production Next.js app                          | Same-origin `/api/c15t` rewrite | Lowers browser startup overhead and keeps the backend origin out of client config |
| Static route, but banner speed matters              | `C15tPrefetch`                  | Starts `/init` before hydration without making the route dynamic                  |
| Dynamic route, or you want the fastest first banner | `fetchInitialData()`            | Starts `/init` on the server and streams the result into the provider             |
| You want the simplest setup                         | Client-only init                | No extra moving parts, but the banner appears later on cold loads                 |

> ℹ️ **Info:**
> C15tPrefetch is the only static-route prefetch step you need in @c15t/nextjs. Matching prefetched data is consumed automatically during first initialization.
>
> ℹ️ **Info:**
> Prefetched or SSR data is reused only when the request context still matches at runtime. That includes the backend URL, credentials, overrides, and the browser's ambient GPC signal.

In production benchmarks with a same-origin rewrite, prefetching strategies show measurable improvement over client-only init:

| Strategy                  | Scripts loaded | Data request starts | Banner visible |
| ------------------------- | -------------- | ------------------- | -------------- |
| Client-only (no prefetch) | baseline       | baseline            | baseline       |
| Browser prefetch          | \~1.3x faster  | \~2.6x earlier      | \~1.25x faster |
| Server prefetch           | \~2x faster    | before page loads   | \~1.9x faster  |

## 1) Prefer Same-Origin Rewrites

Proxy c15t requests through your Next.js app so the browser calls your own origin instead of a third-party domain.

```ts title="next.config.ts"
import type { NextConfig } from 'next';

const config: NextConfig = {
  async rewrites() {
    return [
      {
        source: '/api/c15t/:path*',
        destination: `${process.env.NEXT_PUBLIC_C15T_URL}/:path*`,
      },
    ];
  },
};

export default config;
```

Then use:

```tsx
<ConsentManagerProvider options={{ backendURL: '/api/c15t', mode: 'hosted' }}>
```

Why this helps:

* Same-origin requests avoid extra DNS/TLS setup in many deployments
* Ad blockers are less likely to block your init endpoint
* You can change backend infrastructure without touching client code

> ℹ️ **Info:**
> Set NEXT\_PUBLIC\_C15T\_URL in .env to your backend URL, for example https\://your-project.inth.app.
>
> ℹ️ **Info:**
> Use rewrites for browser-side calls (ConsentManagerProvider, C15tPrefetch). For server-side fetchInitialData(), prefer a direct backend URL (for example https\://your-project.inth.app) to avoid an extra server proxy hop.

## 2) Choose A Startup Strategy

Start with a same-origin rewrite and the default client-side provider. Add one of the preloading strategies below only when the route behavior or performance target calls for it.

### Client-Only Init

Keep the default provider setup when you want the least complexity. This works on both static and dynamic routes, but the banner only appears after the client runtime starts and the initial `/init` request completes.

### Dynamic Routes: Fetch On The Server And Stream

Use `fetchInitialData()` when the route is already dynamic, or when you are willing to make it dynamic in exchange for the fastest first banner.

Because it depends on `next/headers`, this opts the route into dynamic rendering.

```tsx title="app/layout.tsx"
import { fetchInitialData } from '@c15t/nextjs';
import ConsentManager from '@/components/consent-manager';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const ssrData = fetchInitialData({
    backendURL: process.env.NEXT_PUBLIC_C15T_URL!,
  });

  return (
    <html lang="en">
      <body>
        <ConsentManager ssrData={ssrData}>{children}</ConsentManager>
      </body>
    </html>
  );
}
```

```tsx title="components/consent-manager/provider.tsx"
'use client';

import { type ReactNode } from 'react';
import {
  ConsentManagerProvider,
  ConsentBanner,
  ConsentDialog,
  type InitialDataPromise,
} from '@c15t/nextjs';

export default function ConsentManager({
  children,
  ssrData,
}: {
  children: ReactNode;
  ssrData?: InitialDataPromise;
}) {
  return (
    <ConsentManagerProvider
      options={{
        mode: 'hosted',
        backendURL: '/api/c15t',
        ssrData,
      }}
    >
      <ConsentBanner />
      <ConsentDialog />
      {children}
    </ConsentManagerProvider>
  );
}
```

> ℹ️ **Info:**
> Do not await fetchInitialData(). Pass the unresolved Promise to the provider so Next.js can stream the route while /init runs in parallel.
>
> ℹ️ **Info:**
> For fetchInitialData(), prefer a direct backend URL such as https\://your-project.inth.app instead of a rewrite to avoid an extra server-side proxy hop. See Server-Side Data Fetching for the full flow.

### Static Routes: Start Fetch Early In The Browser

Use `C15tPrefetch` when the route needs to stay static but you still want the `/init` request to start before hydration. Matching prefetched data is consumed automatically by the runtime during first store initialization.

```tsx title="app/layout.tsx"
import { C15tPrefetch } from '@c15t/nextjs';
import { ConsentManager } from '@/components/consent-manager';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <C15tPrefetch
          backendURL="/api/c15t"
          overrides={{ country: 'DE', region: 'BE', language: 'de' }}
        />
      </head>
      <body>
        <ConsentManager>{children}</ConsentManager>
      </body>
    </html>
  );
}
```

```tsx title="components/consent-manager/provider.tsx"
'use client';

import {
  ConsentManagerProvider,
  ConsentBanner,
  ConsentDialog,
} from '@c15t/nextjs';

export default function ConsentManagerClient({ children }: { children: React.ReactNode }) {
  return (
    <ConsentManagerProvider
      options={{
        mode: 'hosted',
        backendURL: '/api/c15t',
        overrides: { country: 'DE', region: 'BE', language: 'de' },
      }}
    >
      <ConsentBanner />
      <ConsentDialog />
      {children}
    </ConsentManagerProvider>
  );
}
```

> ℹ️ **Info:**
> C15tPrefetch uses Next.js beforeInteractive script loading, so the /init request can start before hydration.
>
> ℹ️ **Info:**
> If the request context changes between prefetch time and runtime, c15t falls back to a normal client /init. A common example is overrides.gpc conflicting with the browser's ambient GPC signal.

## Keep The Provider Mounted Across Navigation

Mount the consent provider at the app root so route transitions do not remount it.

Why this helps:

* Avoids re-running init work on client-side navigation
* Prevents extra callback churn from remount cycles
* Keeps banner/dialog state stable between route transitions

## Animation Performance

The default motion tokens are tuned for speed-first product UI:

| Token    | Duration | Used for                                                            |
| -------- | -------- | ------------------------------------------------------------------- |
| `fast`   | 80ms     | Banner slide + overlay, card scale, button hover, widget entry/exit |
| `normal` | 150ms    | Accordion, switch toggle                                            |
| `slow`   | 200ms    | Dialog trigger snap, tab indicator                                  |

These defaults follow the principle that product UI should be fast and purposeful — animations exist for spatial continuity, not decoration. In benchmarks, animation duration contributes a constant floor to "data fetched → banner visible" timing. The default tokens sit at the lower end of standard UI ranges (80-200ms) to minimize that floor.

To customize motion durations and easing, see [Styling](/docs/frameworks/next/styling/overview).

## Reduce Network Overhead

If you must use a cross-origin backend URL, add preconnect so the browser starts DNS/TLS early:

```tsx title="app/layout.tsx"
<head>
  <link rel="preconnect" href="https://your-project.inth.app" crossOrigin="" />
</head>
```
