# Nuxt (/docs/examples/nuxt)

This example runs a server side A/B test with the [Nuxt SDK](/docs/sdk/nuxt). A server middleware decides the variant before the page renders, so the first paint already matches the visitor's variant: no layout shift. The [Vue SDK](/docs/sdk/vue) is still used in the browser to record the exposure and post analytics.

## Install [#install]

```bash
npm i @obelism/improve-sdk @obelism/improve-sdk-nuxt @obelism/improve-sdk-vue
```

## Shared config [#shared-config]

A small shared config keeps the organization and environment in one place for both the middleware and the provider. Nuxt's `shared/` folder is for code both sides import.

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

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

The API token stays on the server, in the private runtime config, filled from `NUXT_IMPROVE_API_TOKEN`:

```ts title="nuxt.config.ts"
export default defineNuxtConfig({
	runtimeConfig: {
		improveApiToken: '',
	},
})
```

## Middleware [#middleware]

The middleware creates an [`ImproveServerSDK`](/docs/sdk/javascript#server) and hands it to [`generateImproveNuxtMiddleware`](/docs/sdk/nuxt#generator), with the route each server test runs on. It fetches the datafile on the first request.

```ts title="server/middleware/improve.ts"
import { ImproveServerSDK } from '@obelism/improve-sdk/server'
import { generateImproveNuxtMiddleware } from '@obelism/improve-sdk-nuxt'

import { IMPROVE_CONFIG } from '#shared/improveConfig'

const improveSdk = new ImproveServerSDK({
	...IMPROVE_CONFIG,
	token: () => useRuntimeConfig().improveApiToken,
	// Pages wait for the datafile: fail fast rather than retry.
	configRetries: 0,
})

export default generateImproveNuxtMiddleware({
	improveSdk,
	serverABtests: [{ slug: 'startpage-visual', routeHandler: '/' }],
})
```

## Client provider [#client-provider]

The provider loads the client SDK and its config after mount. Generate it once and export what the pages use:

```ts title="app/improve.ts"
import { generateImproveProvider } from '@obelism/improve-sdk-vue'

import { IMPROVE_CONFIG } from '#shared/improveConfig'

export const { ImproveProvider, useImproveStatus, useTestValue, usePostAnalytic } =
	generateImproveProvider(IMPROVE_CONFIG)
```

Wrap the app with the provider:

```vue title="app/app.vue"
<script setup lang="ts">
import { ImproveProvider } from './improve'
</script>

<template>
	<ImproveProvider>
		<NuxtPage />
	</ImproveProvider>
</template>
```

## The page [#the-page]

Unlike [Next.js](/docs/examples/nextjs), there is no route per variant: Nuxt hydrates the route the browser shows, so the page renders the variant itself. `useState` hands the server's decision to the browser, and [`useTestValue`](/docs/sdk/vue#usetestvalue) renders it: the server's value as the fallback, then the same value read from the cookie once the SDK is set up. After a client-side navigation no request reached the middleware, and the browser decides.

```vue title="app/pages/index.vue"
<script setup lang="ts">
import { getImproveContext } from '@obelism/improve-sdk-nuxt'

import { useTestValue } from '~/improve'

const decided = useState('improve:startpage-visual', () => {
	const event = useRequestEvent()
	return (event && getImproveContext(event)['startpage-visual']) || null
})
// Reading the variant in the browser records the exposure events are attributed to.
const variant = useTestValue('startpage-visual', decided.value ?? 'control')

useTrackPageView('homepage')
</script>

<template>
	<main>
		<Visual :variant="variant === 'variation' ? 'variation' : 'control'" />
	</main>
</template>
```

## Tracking [#tracking]

The page posts analytics with [`usePostAnalytic`](/docs/sdk/vue#usepostanalytic). 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. `useTrackPageView` reports a page load once the SDK is set up, `Visual` reports a click on the variant.

```ts title="app/composables/useTrackPageView.ts"
import { useImproveStatus, usePostAnalytic } from '~/improve'

export const useTrackPageView = (page: string) => {
	const status = useImproveStatus()
	const postAnalytic = usePostAnalytic()

	// Nothing is sent before the SDK is set up.
	watch(
		status,
		(current) => {
			if (current === 'setup') postAnalytic('page_view', page)
		},
		{ immediate: true },
	)
}
```

```vue title="app/components/Visual.vue"
<script setup lang="ts">
import { usePostAnalytic } from '~/improve'

defineProps<{ variant: 'control' | 'variation' }>()
const postAnalytic = usePostAnalytic()
</script>

<template>
	<img
		src="/nuxt.svg"
		alt="Nuxt logo"
		width="180"
		height="37"
		:style="variant === 'control' ? {} : { transform: 'scaleX(-1)' }"
		@click="postAnalytic('visual_click', 'homepage')"
	/>
</template>
```

Keep tested routes out of prerendering and route caches (`prerender`, `swr`, `isr` in `routeRules`): a cached page shows one variant to everyone. The middleware marks a tested page `Cache-Control: private` for the same reason.
