← Files UniformARCHIVED FILE
skills/uniform-nextjs-app-router/references/personalization.md
4.82 KB · Oct 4, 2026 · 12:32 UTC
# Personalization
Personalization and A/B testing are configured in the Uniform dashboard and evaluated automatically at the edge by the middleware. No component code is required — the SDK renders the winning variant transparently.
Client-side hooks let you read and update the visitor's Uniform context (quirks and scores). Any component using these hooks or browser APIs must be marked `"use client"`.
**Push client components as far down the tree as possible.** Adding `"use client"` turns a component and all its children into client components (more JavaScript, no SSR for that subtree). Extract the interactive piece (e.g. a button that updates a quirk) into its own small client component embedded in a server-rendered parent.
## useUniformContext hook
Access and update the context on the client. The `context` object may be `undefined` while the client-side context initializes — always check before using it:
```tsx
"use client";
import { useUniformContext } from "@uniformdev/next-app-router/component";
import { useEffect, useState } from "react";
export const QuirkButton = () => {
const { context } = useUniformContext();
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
if (context?.quirks !== undefined) {
setIsLoading(false);
}
}, [context?.quirks]);
const updateQuirk = async () => {
setIsLoading(true);
try {
await context?.update({
quirks: { country: "Canada" },
});
} finally {
setIsLoading(false);
}
};
return (
<button onClick={updateQuirk} disabled={isLoading}>
Set Country to Canada
</button>
);
};
```
## useQuirks hook
Reactive access to visitor quirk values (re-renders when quirks change):
```tsx
"use client";
import { useQuirks } from "@uniformdev/next-app-router/component";
export const LocationBanner = () => {
const quirks = useQuirks();
return <div>Current country: {quirks?.country ?? "Unknown"}</div>;
};
```
## useScores hook
Reactive access to visitor score values:
```tsx
"use client";
import { useScores } from "@uniformdev/next-app-router/component";
export const InterestIndicator = () => {
const scores = useScores();
return <div>Tech interest score: {scores?.tech ?? 0}</div>;
};
```
## Custom client context component
For advanced scenarios (custom Context plugins, analytics integrations, custom dev tools behavior), supply a custom `clientContextComponent` to `UniformComposition`. Note the import path: `@uniformdev/next-app-router-client`.
```tsx
"use client";
import { ContextPlugin, enableContextDevTools } from "@uniformdev/context";
import { useRouter } from "next/navigation";
import {
createClientUniformContext,
useInitUniformContext,
ClientContextComponent,
} from "@uniformdev/next-app-router-client";
export const CustomUniformClientContext: ClientContextComponent = ({
manifest,
disableDevTools,
defaultConsent,
experimentalQuirkSerialization,
compositionMetadata,
}) => {
const router = useRouter();
useInitUniformContext(() => {
const plugins: ContextPlugin[] = [];
if (!disableDevTools) {
plugins.push(
enableContextDevTools({
onAfterMessageReceived: () => {
router.refresh();
},
})
);
}
return createClientUniformContext({
manifest,
plugins,
defaultConsent,
experimental_quirksEnabled: experimentalQuirkSerialization,
});
}, compositionMetadata);
return null;
};
```
Pass it to the composition route (see `references/setup.md`):
```tsx
<UniformComposition
code={code}
resolveRoute={resolveRouteFromCode}
resolveComponent={resolveComponent}
clientContextComponent={CustomUniformClientContext}
/>
```
## Server-side precomputation
To evaluate tests and personalizations server-side (for fully static output), use `precomputeComposition`. It walks the composition tree and replaces personalization and test containers with their resolved (winning) variants. Selectively control what to evaluate with filter functions:
```tsx
import { precomputeComposition } from "@uniformdev/next-app-router";
await precomputeComposition({
pageState: result.pageState,
route: result.route,
evaluateTests: true, // or a filter function
evaluatePersonalizations: (pz) => pz.name !== "skip-this-one",
});
```
## Setting quirks in middleware
Quirks can also be injected server-side in middleware (e.g. from headers or cookies) — see `references/routing.md`. On Vercel, geo-IP quirks are populated automatically (below).
## Vercel geo-IP quirks
On Vercel, the middleware automatically populates quirks from geo-IP headers — available for personalization rules with no extra configuration:
| Header | Quirk key |
|--------|-----------|
| `x-vercel-ip-country` | `vc-country` |
| `x-vercel-ip-country-region` | `vc-region` |
| `x-vercel-ip-city` | `vc-city` |
SHA-256: 013c51503723393a1f681c31e59abd3eb9c54c385695212b72f9db9201821ccc