---
title: Optimization
description: Improve c15t startup performance in Next.js with same-origin rewrites, static prefetching, and dynamic-route SSR.
lastModified: 2026-04-14
---
<import src="../../shared/react/guides/optimization.mdx#intro" />

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

<import src="../../shared/react/guides/optimization.mdx#benchmark" />

## 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' }}>
```

<import src="../../shared/react/guides/optimization.mdx#rewrites-why" />

> ℹ️ **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.

<import src="../../shared/react/guides/optimization.mdx#keep-mounted" />

<import src="../../shared/react/guides/optimization.mdx#animation" />

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

## Reduce Network Overhead

<import src="../../shared/react/guides/optimization.mdx#preconnect" />

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