> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://contentful.com/developers/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://contentful.com/developers/docs/_mcp/server.

# Architecture and how it works

> Understand how Contentful Personalization works, the content model, rendering flow, and how components are personalized.

This guide explains how Contentful Personalization works at a technical level. Understanding the architecture will help you make better implementation decisions and avoid common setup issues.

## How personalization works

Contentful Personalization delivers personalized content by wrapping your existing components with an `<Experience>` component in React. This wrapper checks which audiences the current visitor belongs to and swaps the baseline content for the matching variant. Your component receives different Contentful entry data, but the component itself does not change.

## Content types

The Contentful Personalization app installs three content types in your space:

### 1. `nt_experience`

An experience defines a personalization rule or experiment:

* **Audience**: Which visitors to target (a reference to an `nt_audience` entry).
* **Variants**: Alternative content entries or [custom flag change types](https://www.contentful.com/help/personalization/experiences/custom-flags/) to display. The `nt_variants` field is a collection of all possible variants that an Optimization baseline has.
* **Distribution**: The traffic split between variants (for example, 50/50 for an A/B test).
* **Type**: Either `nt_personalization` (deterministic, targets a specific audience) or `nt_experiment` (random split for A/B testing).

### 2. `nt_audience`

An audience defines the targeting rules that determine which visitors qualify. Audiences are evaluated server-side by the Experience API based on visitor attributes such as location, traits, behavior, and session data. You [create and manage audience rules](https://www.contentful.com/help/personalization/audiences/) in the Contentful web app.

### 3. `nt_mergetag`

A merge tag maps a display name to a path in the visitor's profile. For example, a merge tag named "City" might resolve to `location.city`, or "First Name" to `traits.firstName`. Content editors embed merge tag entries in rich text fields for inline personalization. Developers can also use the `<MergeTag>` component directly in JSX (JavaScript XML).

## The `nt_experiences` field

When you configure a content type for personalization, the app adds an `nt_experiences` field which is an array of references to `nt_experience` entries. This field links your content to the experiences that can personalize it.

For example, a **Hero** content type with an `nt_experiences` field can have one experience that shows a different headline to visitors from Germany, and another that runs an A/B test on the call-to-action text.

## The rendering flow

The typical flow from Contentful content to a personalized component looks like this:

1. **Fetch entries** from the Contentful API with an include depth of at least 2 (see [why include depth matters](#why-include-depth-matters) below).
2. **Extract and map experiences** from each entry using the utility library:
   * Filter with `ExperienceMapper.isExperienceEntry()`
   * Map with `ExperienceMapper.mapExperience()`
3. **Wrap the component** with `<Experience>`, passing the mapped experiences and the entry's fields.
4. **The SDK resolves** which variant to show by checking the visitor's profile against each experience's audience and selecting a variant based on the distribution weights. This happens through the [Experience API](/references/experience-api/overview/). The component renders with either the variant or the baseline content.

For details on the `ExperienceMapper` utilities, see the [Experience SDK](/personalization/experience-sdk#utility-libraries) documentation.

### Why include depth matters

When Contentful resolves references in API responses, the `include` parameter controls how many levels deep it goes:

* **`include: 1`** — resolves the entry's direct references, but `nt_experiences` entries appear as unresolved links.
* **`include: 2`** — resolves experience entries, but variant entries within them may not fully resolve.
* **`include: 3` or higher** — resolves the full chain: entry → experience → variant content.

If the include depth is too low, `ExperienceMapper.isExperienceEntry()` filters out unresolved entries and personalization silently does nothing. The component always shows the baseline.

> **Info**
>
> **NOTE**:
> We recommend using `include: <your_current_include> + 3` or higher. Complex page structures with nested sections often use `include: 10`.

## The component mapper pattern

Most Contentful Personalization implementations use a component mapper. A component mapper is an object that maps Contentful content type IDs to React components. A renderer component then wraps each entry with `<Experience>` automatically:

```tsx
const ContentTypeMap = {
  hero: Hero,
  cta: CTA,
  feature: Feature,
  banner: Banner,
};

const BlockRenderer = ({ block }) => {
  const contentTypeId = block.sys.contentType.sys.id;
  const Component = ContentTypeMap[contentTypeId];
  if (!Component) return null;

  const experiences = (block.fields.nt_experiences || [])
    .filter(ExperienceMapper.isExperienceEntry)
    .map(ExperienceMapper.mapExperience);

  return (
    <Experience
      {...block}
      id={block.sys.id}
      component={ComponentRenderer}
      experiences={experiences}
    />
  );
};
```

This approach centralizes personalization in one place. Every component that flows through the renderer is automatically wrapped with `<Experience>`, so you don't have to add wrappers manually in each page template.

## Client-side vs. server-side personalization

### Client-side (default)

With client-side personalization, the server sends static HTML with baseline content. After the page loads, the SDK contacts the Experience API, resolves audiences, and swaps the components to show the correct variants.

> **Info**
>
> **NOTE**:
> There are also client-side implementations where the server returns no baseline at all, and the entire baseline/variant rendering logic happens on the client side.

Client-side personalization is the simplest approach and requires no server or edge infrastructure beyond what you already have.

### Server-side and edge (SSR/ESR)

With server-side or edge-side personalization, the server or edge middleware contacts the Experience API before rendering HTML. The visitor receives pre-personalized HTML. There is no flash of default content.

This approach requires additional infrastructure:

* Middleware or an edge worker to call the Experience API
* The `@ninetailed/experience.js-plugin-ssr` package for profile continuity
* Cookie management for the `ntaid` profile identifier
* Preflight mode (`?type=preflight`) to prevent double-counting events when a client SDK also runs

For implementation details, see [Edge and Server Side Rendering](/personalization/edge-and-server-side-rendering).

## The provider

`NinetailedProvider` is a React context provider that initializes the SDK and manages the visitor's state. It handles:

* Creating the Ninetailed instance with your API key.
* Managing the visitor's profile (traits, audiences, sessions).
* Communicating with the Experience API to resolve variant selections.
* Providing React hooks (`useProfile`, `useNinetailed`) to child components.
* Loading plugins for analytics, preview, SSR, and privacy.

The provider must wrap all components you want to personalize. It is typically placed in `pages/_app.tsx` for Next.js Pages Router projects, or in a client component wrapper around the root layout for App Router projects.

> **Info**
>
> **NOTE**:
> If your implementation is 100% server-side, do not include `NinetailedProvider` because a React provider can only run on the client-side.

For provider configuration details, see the [React SDK](/personalization/react-sdk) documentation.