# Next.js

`@splithub/next` covers the App Router (Next.js 14+): a middleware for the visitor cookie and redirect experiments, helpers for Server Components, and a client provider that agrees with the server on the first byte. `npx splithub init` creates the middleware and prints a provider snippet; add the layout integration below.

```bash
npm i @splithub/next @splithub/react @splithub/core
```

## Middleware

```ts
// middleware.ts
import { splithubMiddleware } from '@splithub/next';
import splitHubConfig from './splithub.config';

export default splithubMiddleware(splitHubConfig);
export const config = { matcher: ['/((?!_next|api|.*\\..*).*)'] };
```

It sets the `sh_vid` cookie, passes the id to Server Components in a header, rewrites the request for `redirect` experiments (the URL in the address bar does not change, the visitor sees no flash) and records edge exposures. In cloud mode it fetches live overrides at most once per 60 s per isolate; opt out with `splithubMiddleware(splitHubConfig, { remote: false })`.

## Layout

```tsx
// app/layout.tsx (Server Component)
import { SplitHubNextProvider } from '@splithub/next/client';
import { getSplitHub } from '@splithub/next/server';
import config from '../splithub.config';

export default async function RootLayout({ children }) {
  const sh = await getSplitHub(config);
  return (
    <html>
      <body>
        <SplitHubNextProvider config={config} visitorId={sh.visitorId ?? undefined} remoteOverrides={sh.overrides}>
          {children}
        </SplitHubNextProvider>
      </body>
    </html>
  );
}
```

## Client Components

Same API as `@splithub/react`, imported from `@splithub/next/client`: `Experiment`, `Variant`, `useVariant`, `useValue`, `useTrack`, plus `Exposure`.

## Server Components

```tsx
import { getSplitHub } from '@splithub/next/server';
import { Exposure } from '@splithub/next/client';
import config from '../../splithub.config';

export default async function Page() {
  const sh = await getSplitHub(config);
  const title = sh.value<string>('hero-title');
  return (
    <>
      <h1>{title}</h1>
      <Exposure id="hero-title" />
    </>
  );
}
```

`getSplitHub(config)` gives `.variant(id)` and `.value(id)` computed from the cookie the middleware set. `<Exposure>` records the exposure once on the client, so a server-rendered variant counts exactly like a client-rendered one.

## Self-hosted ingest route

Install `@splithub/node` and `@splithub/adapter-sqlite` for this example. SQLite requires Node.js 22.13 or newer.

```ts
// app/api/splithub/[[...path]]/route.ts
import { createSplitHubHandler } from '@splithub/node';
import { sqliteStore } from '@splithub/adapter-sqlite';
import config from '../../../../splithub.config';

const handler = createSplitHubHandler({ config, store: sqliteStore(), adminToken: process.env.SPLITHUB_ADMIN_TOKEN });
export const POST = handler;
export const GET = handler;
export const runtime = 'nodejs';
```

In cloud mode this file is not needed.

## Redirect experiments

Declare `kind: 'redirect'` with `match` and a `to` per variant; no code. The middleware rewrites `/` to `/start` for the `new-landing` variant and records the exposure at the edge. Details and the drop-in alternative: [Redirect experiments](/docs/redirect-experiments).

## Why there is no flicker

The middleware decides the visitor id before anything renders. Server Components compute the same deterministic assignment as the client, and the client provider receives that id and the same overrides as props. Both sides agree on the first byte, so hydration never swaps a variant.
