> This page is for Experience Orchestration.

> 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.

# Get started with the Experiences SDK

> Go from an empty Next.js project to a rendered experience with the Experiences SDK.

> **Info**
>
> Experiences SDK is currently available in alpha. APIs are unstable and subject to change.

This guide walks you from an empty Next.js project to a rendered experience. You set up the SDK first, before you have an experience or any component IDs, and add them as you go. The experience renders on the server, so your page ships only the JavaScript your components need.

The guide uses Next.js with the App Router. The SDK also has adapters for SvelteKit and Angular. See [Next steps](#next-steps).

## Prerequisites

* **Node.js 22.12 or later**
* **A Contentful space with Experience Orchestration enabled**
* **The Content Delivery API access token** for the space: find it in **Settings > API keys** in the Contentful web app. To preview draft content in step 9, you also need the Content Preview API access token from the same page.

You don't need an experience yet. Step 5 shows how to create one or seed a demo experience.

## 1. Create a Next.js app

```sh
npx create-next-app@15 my-experiences-app --yes --app --use-npm
cd my-experiences-app
```

This guide uses Next.js 15, the version the [Next.js example](https://github.com/contentful/experiences/tree/main/examples/nextjs) uses.

## 2. Install the SDK

To install the SDK, run the following command:

```sh
npm install @contentful/experiences-react
```

`@contentful/experiences-react` is the only package you install. It includes the delivery client, the resolver, the renderers, and the types.

Add your space credentials to `.env.local` in the project root:

```sh
SPACE_ID=<space-id>
ENVIRONMENT_ID=<environment-id>
CDA_TOKEN=<content-delivery-api-token>
```

`ENVIRONMENT_ID` is the environment that holds your experiences, usually `master`.

## 3. Create the SDK configuration

The configuration maps the component IDs authored in Contentful to your React components. You don't have any components registered yet, so start with an empty registry:

```ts
// lib/experience-config.ts
import type { Config } from '@contentful/experiences-react';

export const experienceConfig: Config = {
  components: {},
};
```

You add components to `components` in step 6.

## 4. Add an experience route

Create a route that fetches an experience by ID and renders it on the server:

```tsx
// app/[slug]/page.tsx
import { notFound } from 'next/navigation';
import { fetchExperience, NotFoundError, ServerExperienceRenderer } from '@contentful/experiences-react';

import { experienceConfig } from '../../../../lib/experience-config';

export default async function ExperiencePage({
  params,
  searchParams,
}: {
  params: Promise<{ slug: string }>;
  searchParams: Promise<{ debug?: string }>;
}) {
  const { slug: experienceId } = await params;
  const { debug } = await searchParams;

  let experience;
  try {
    experience = await fetchExperience(
      {
        spaceId: process.env.SPACE_ID!,
        environmentId: process.env.ENVIRONMENT_ID!,
        experienceId,
        locale: 'en-US',
      },
      { accessToken: process.env.CDA_TOKEN! },
      { config: experienceConfig, debug: debug === 'true' }
    );
  } catch (error) {
    if (error instanceof NotFoundError) notFound();
    throw error;
  }

  return <ServerExperienceRenderer experience={experience} config={experienceConfig} />;
}
```

`fetchExperience` fetches the experience from the Experience Delivery API and resolves it. `ServerExperienceRenderer` renders it in a React Server Component. The experience ID comes from the URL, so you don't need one in your code. `locale` selects the content locale; use one of your space's locales. `?debug=true` turns on debug mode, which you use in the next step.

> **Info**
>
> An unknown experience ID throws `NotFoundError`, which the route turns into a 404 page.

## 5. Render your first experience

You need a published experience in your space. Either:

* **Create one** in the Contentful web app and publish it.
* **Seed the demo experience** with the bootstrap script from the [Experiences SDK repository](https://github.com/contentful/experiences/tree/main/examples/scripts). It creates a `landing` experience with its components, content, and design tokens.

Start the development server:

```sh
npm run dev
```

Open `http://localhost:3000/<experience-id>?debug=true`, using the experience's ID from Contentful, or `landing` for the demo.

Your registry is still empty, so each component renders as a **Missing component** box that names its component ID, such as `Section` or `hero-plain`, and shows the key to register. Those IDs are the keys you register in the next step. Without debug mode, unregistered components render nothing.

The boxes only show the components at the top of the experience at first. Components nested in a slot of an unregistered component appear once you register their parent. Debug mode also logs the fetch and adds a panel after the experience with the resolved experience.

## 6. Register your components

Write a React component for each component ID. Content properties and design properties arrive as ordinary props, under the names authored in Contentful:

```tsx
// components/Heading.tsx
import type { CSSProperties } from 'react';

export interface HeadingProps {
  text?: string;
  // Design properties, resolved and passed as props.
  as?: 'h1' | 'h2' | 'h3';
  fontSize?: string;
  fontWeight?: string;
}

export function Heading({ text, as = 'h2', fontSize, fontWeight }: HeadingProps) {
  const Tag = as;
  const style: CSSProperties = { fontSize, fontWeight };
  return <Tag style={style}>{text}</Tag>;
}
```

Register it under its component ID:

```ts
// lib/experience-config.ts
import type { Config } from '@contentful/experiences-react';

import { Heading } from '../../../../components/Heading';

export const experienceConfig: Config = {
  components: {
    Heading,
  },
};
```

Reload the page. The heading renders, and its placeholder is gone. Register the rest of the components the same way. A component's prop names are the content and design property names of its definition in Contentful. The debug panel shows each node's `content` and `design` keys.

A component with a slot, such as a section that contains other components, receives the slot's rendered children as a prop named after the slot. The default slot is usually named `children`:

```tsx
// components/Section.tsx
import type { ReactNode } from 'react';

export interface SectionProps {
  children?: ReactNode[];
  gap?: string;
  backgroundColor?: string;
}

export function Section({ children, gap, backgroundColor }: SectionProps) {
  return <section style={{ display: 'flex', flexDirection: 'column', gap, backgroundColor }}>{children}</section>;
}
```

The registry key is the last segment of the component's URN in Contentful. For `crn:contentful:::experience:spaces/$self/environments/$self/components/Heading`, the key is `Heading`. Experience templates go in a separate `experienceTemplates` registry. See [Register components](/developers/docs/experience-orchestration/experiences-sdk/register-components).

## 7. Resolve design tokens

A design property can hold a design token, such as `size.lg`, instead of a value. Map token IDs to the values your components use with `resolveToken`:

```ts
// lib/experience-config.ts
import type { Config, ResolveToken } from '@contentful/experiences-react';

import { Heading } from '../../../../components/Heading';

const designTokens: Record<string, string> = {
  'size.lg': '40px',
  'color.primary': '#0f172a',
  'fontWeight.bold': '700',
};

const resolveToken: ResolveToken = (token) => designTokens[token.value];

export const experienceConfig: Config = {
  components: { Heading },
  resolveToken,
};
```

Use the token IDs and values of your own design system. A token with no mapping reaches the component as a raw object, `{ type: 'DesignToken', value: '<token-id>' }`, which renders as `[object Object]` in a style. Debug mode logs every token ID without a mapping, so you can find the ones to add. See [Style components](/developers/docs/experience-orchestration/experiences-sdk/style-components) for CSS custom properties, Tailwind, and other token sources.

## 8. Use the SDK hooks

Most components only need their props. Two patterns cover the common exceptions.

### Read design values in a nested child

A child that isn't a registered component gets no props from the SDK. `useDesignValues()` reads the design values of the nearest registered component:

```tsx
// components/Card.tsx
'use client';

import { useDesignValues } from '@contentful/experiences-react';

export interface CardProps {
  title?: string;
  ctaLabel?: string;
  ctaUrl?: string;
  backgroundColor?: string;
  color?: string;
}

export function Card({ title, ctaLabel, ctaUrl, backgroundColor, color }: CardProps) {
  return (
    <article style={{ backgroundColor, color }}>
      <h3>{title}</h3>
      {ctaLabel && ctaUrl && <CardCta label={ctaLabel} url={ctaUrl} />}
    </article>
  );
}

// Not registered with the SDK, so it reads the card's design from context.
function CardCta({ label, url }: { label: string; url: string }) {
  const { color } = useDesignValues<{ color?: string }>();
  return (
    <a href={url} style={{ background: color ?? '#111', color: '#fff' }}>
      {label}
    </a>
  );
}
```

A component that calls an SDK hook, or uses state or effects, must be a client component. Add `'use client'` at the top of its file. Components that only read props can stay server components.

### Load data before rendering

`resolveData` derives props from external data, such as a price from your commerce API. It runs on the server inside `fetchExperience`, before the experience renders, and its return value is merged into the component's props.

This example assumes the card has an `sku` content property. Add a `price` prop to `CardProps` and render it in `Card`, then register the card with `defineComponent`, keeping the components and `resolveToken` from the earlier steps:

```ts
// lib/experience-config.ts
import { defineComponent, type Config } from '@contentful/experiences-react';

import { Card, type CardProps } from '../../../../components/Card';
import { Heading } from '../../../../components/Heading';
import { Section } from '../../../../components/Section';

async function fetchPrice(sku: string): Promise<string | undefined> {
  const response = await fetch(`https://commerce.example.com/prices/${sku}`);
  if (!response.ok) return undefined;
  const { price } = await response.json();
  return price;
}

export const experienceConfig: Config = {
  components: {
    Heading,
    Section,
    card: defineComponent<CardProps & { price?: string }>({
      component: Card,
      resolveData: async ({ content }) => ({
        price: await fetchPrice(content.sku as string),
      }),
    }),
  },
  resolveToken, // from step 7
};
```

`defineComponent` registers a component with `resolveData` or default props. The SDK runs every `resolveData` function in parallel. If one throws, that component renders without the resolved data, and the rest of the experience renders normally.

## 9. Preview draft content

To render unpublished changes, fetch from the Content Preview API. Add the Content Preview API access token to `.env.local`:

```sh
CPA_TOKEN=<content-preview-api-token>
```

Pass the token and a `preview` flag to `fetchExperience`. This example turns on preview with `?preview=true`. In **app/\[slug]/page.tsx**, read `preview` from the search params alongside `debug`, then update the fetch:

```tsx
const { debug, preview } = await searchParams; // type searchParams as { debug?: string; preview?: string }

const experience = await fetchExperience(
  {
    spaceId: process.env.SPACE_ID!,
    environmentId: process.env.ENVIRONMENT_ID!,
    experienceId,
    locale: 'en-US',
  },
  {
    accessToken: process.env.CDA_TOKEN!,
    previewToken: process.env.CPA_TOKEN,
    preview: preview === 'true',
  },
  { config: experienceConfig, debug: debug === 'true' }
);
```

To preview unsaved changes inside the Contentful canvas, see [Set up live preview](/developers/docs/experience-orchestration/experiences-sdk/live-preview).

## 10. Render on the client when you need to

Server rendering is the default. Use `ClientExperienceRenderer` only when the experience itself must change in the browser, for example after refetching it with new parameters:

```tsx
'use client';

import { ClientExperienceRenderer, type PortableRenderPlan } from '@contentful/experiences-react';

import { experienceConfig } from '../../../../lib/experience-config';

export function ExperienceClient({ experience }: { experience: PortableRenderPlan }) {
  return <ClientExperienceRenderer experience={experience} config={experienceConfig} />;
}
```

Fetch the experience in a server component, as in step 4, and pass it to `ExperienceClient` as a prop. The resolved experience is plain data, so it crosses to the client.

A client component that imports `experienceConfig` bundles everything the configuration imports, including server-only code in `resolveData`. If your `resolveData` functions use server-only modules, give the client renderer a configuration with the same components and no `resolveData`.

Interactive components don't need the client renderer. A `'use client'` component hydrates inside `ServerExperienceRenderer`. See [Render experiences](/developers/docs/experience-orchestration/experiences-sdk/render-experiences).

## Next steps

* [Register components](/developers/docs/experience-orchestration/experiences-sdk/register-components) for default props, slots, and experience templates.
* [Fetch experiences](/developers/docs/experience-orchestration/experiences-sdk/fetch-experiences) for locales, parameters, and destinations.
* [Errors and debugging](/developers/docs/experience-orchestration/experiences-sdk/errors-and-debugging) for fallbacks and diagnostics.
* **[Next.js example](https://github.com/contentful/experiences/tree/main/examples/nextjs)**: The complete app this guide is based on.
* **[SvelteKit example](https://github.com/contentful/experiences/tree/main/examples/sveltekit)** and **[Angular example](https://github.com/contentful/experiences/tree/main/examples/angular)**: The same experience in the other adapters.