# Next.js (/docs/examples/nextjs)



This example runs a server side A/B test with the [Next.js SDK](/docs/sdk/nextjs). The middleware decides the variant before the page is rendered and rewrites the request to an internal route, so the first paint already matches the visitor's variant: no layout shift. The [React SDK](/docs/sdk/react) is still used on the client to post analytics.

## Install [#install]

```bash
npm i @obelism/improve-sdk @obelism/improve-sdk-next @obelism/improve-sdk-react
```

## Shared config [#shared-config]

A small shared config keeps the organization and environment in one place for both the middleware and the client provider.

```ts title="src/app/improveConfig.ts"
import { ImproveSetupArgs } from '@obelism/improve-sdk/types'

export const IMPROVE_CONFIG: ImproveSetupArgs = {
	organizationId: 'org_MJFL46Z0WXGQ5OHW1ZXSM3Q88S',
	environment: 'staging',
} as const
```

## Middleware [#middleware]

The middleware creates an [`ImproveServerSDK`](/docs/sdk/javascript#server), fetches the config, and hands the request to [`generateImproveNextMiddleware`](/docs/sdk/nextjs#middleware). Each server test maps a route to the internal slugs visitors are rewritten to.

```ts title="src/middleware.ts"
import { ImproveServerSDK } from '@obelism/improve-sdk/server'
import { generateImproveNextMiddleware } from '@obelism/improve-sdk-next'
import { type NextRequest } from 'next/server'

import { IMPROVE_CONFIG } from './app/improveConfig'

export const config = {
	matcher: '/',
}

const improveSdk = new ImproveServerSDK({
	...IMPROVE_CONFIG,
	token: 'xxxx-xxxx-xxxx-xxxx',
})

const improveMiddlewareHandler = generateImproveNextMiddleware({
	improveSdk,
	serverABtests: [
		{
			slug: 'startpage-visual',
			routeHandler: '/',
			options: [
				{
					value: 'control',
					slug: '/',
				},
				{
					value: 'variation',
					slug: '/variation',
				},
			],
		},
	],
})

export const middleware = async (request: NextRequest) => {
	await improveSdk.fetchConfig()
	return improveMiddlewareHandler(request)
}
```

**Note:** the runnable example hardcodes the server `token` for brevity. In your own app load it from an environment variable so it never ends up in version control.

## Client provider [#client-provider]

The provider has to be a client component because it loads the SDK and fetches the config in a `useEffect`. Re-export it from a `'use client'` file.

```ts title="src/app/improve.ts"
'use client'

import { generateImproveProvider } from '@obelism/improve-sdk-react'
import { IMPROVE_CONFIG } from './improveConfig'

const improveReact = generateImproveProvider(IMPROVE_CONFIG)

export const ImproveProvider = improveReact.ImproveProvider
export const useTestValue = improveReact.useTestValue
export const useFlagValue = improveReact.useFlagValue
export const usePostAnalytic = improveReact.usePostAnalytic
```

Wrap the app with the provider in the root layout:

```tsx title="src/app/layout.tsx"
import { ImproveProvider } from './improve'

export default function RootLayout({
	children,
}: Readonly<{ children: React.ReactNode }>) {
	return (
		<html lang="en">
			<body>
				<ImproveProvider>{children}</ImproveProvider>
			</body>
		</html>
	)
}
```

## Routes [#routes]

Each variant is a normal route. The middleware rewrites `/` to `/variation` for visitors in that variant, so both pages render the same `HomePage` with a different `variant` prop.

```tsx title="src/app/page.tsx"
import { HomePage } from './HomePage'

export default function Home() {
	return <HomePage variant="control" />
}
```

```tsx title="src/app/variation/page.tsx"
import { HomePage } from '../HomePage'

export default function Home() {
	return <HomePage variant="variation" />
}
```

## Tracking [#tracking]

Client components post analytics with [`usePostAnalytic`](/docs/sdk/react#usepostanalytic). It takes no arguments and events are **test-independent**: call it with an event name and a required `scope` and the server attributes the event to whichever tests/flags the visitor was exposed to. Because this example resolves the variant server-side (via middleware rewrite), read the variant client-side with [`useTestValue`](/docs/sdk/react#usetestvalue) so the SDK records the **exposure** that events are attributed to. `TrackPageView` reports a page load, `Visual` reports a click on the variant.

```tsx title="src/app/TrackPageView.tsx"
'use client'

import { useEffect } from 'react'
import { usePostAnalytic, useTestValue } from './improve'

export const TrackPageView = ({ page }: { page: string }) => {
	const postAnalytic = usePostAnalytic()
	// Reading the variant records the exposure events are attributed to.
	useTestValue('startpage-visual')

	useEffect(() => {
		postAnalytic('page_view', page)
	}, [page, postAnalytic])

	return null
}
```

```tsx title="src/app/Visual.tsx"
'use client'

import Image from 'next/image'
import { usePostAnalytic } from './improve'

export const Visual = ({ variant }: { variant: 'control' | 'variation' }) => {
	const postAnalytic = usePostAnalytic()

	return (
		<Image
			src="/next.svg"
			alt="Next.js Logo"
			width={180}
			height={37}
			priority
			style={variant === 'control' ? {} : { transform: 'scaleX(-1)' }}
			onClick={() => {
				postAnalytic('visual_click')
			}}
		/>
	)
}
```

## Source [#source]

<Cards>
  <Card title="View on GitHub" href="https://github.com/Obelism/improve-sdk/tree/main/examples/nextjs" description="Full source for this example" />

  <Card title="View live demo" href="https://nextjs.improve.obelism.studio" description="This example deployed and running" />
</Cards>
