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

# Render experiences

> Render a fetched experience on the server or the client with the Experiences SDK renderers.

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

Each framework adapter has two renderers for a fetched experience: a server renderer and a client renderer. Both take the experience from `fetchExperience` and your configuration, and render every node with your registered components.

## Choose a renderer

Render on the server by default. Use the client renderer only when the experience itself must change in the browser, for example after refetching it with new [parameters](/developers/docs/experience-orchestration/experiences-sdk/fetch-experiences#pass-runtime-parameters) or during [live preview](/developers/docs/experience-orchestration/experiences-sdk/live-preview).

| Framework | Server renderer            | Client renderer                                         |
| --------- | -------------------------- | ------------------------------------------------------- |
| React     | `ServerExperienceRenderer` | `ClientExperienceRenderer` (alias `ExperienceRenderer`) |
| Svelte    | `ServerExperienceRenderer` | `ClientExperienceRenderer` (alias `ExperienceRenderer`) |
| Angular   | `<cf-server-experience>`   | `<cf-experience>`                                       |

In React, `ServerExperienceRenderer` works in React Server Components. Interactive components don't need the client renderer. A component marked `'use client'` hydrates inside `ServerExperienceRenderer`, and the rest of the page stays server-rendered.

#### React

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

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

export default async function ExperiencePage({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const experience = await fetchExperience(
    { spaceId: process.env.SPACE_ID!, environmentId: 'master', experienceId: slug },
    { accessToken: process.env.CDA_TOKEN! },
    { config: experienceConfig }
  );
  return <ServerExperienceRenderer experience={experience} config={experienceConfig} />;
}
```

#### Svelte

```ts
// routes/[slug]/+page.server.ts
import { fetchExperience } from '@contentful/experiences-svelte';
import { CDA_TOKEN, SPACE_ID } from '$env/static/private';
import { experienceConfig } from '$lib/experience-config';

export const load = async ({ params }) => {
  const experience = await fetchExperience(
    { spaceId: SPACE_ID, environmentId: 'master', experienceId: params.slug },
    { accessToken: CDA_TOKEN },
    { config: experienceConfig }
  );
  return { experience };
};
```

```svelte
<!-- routes/[slug]/+page.svelte -->
<script lang="ts">
  import { ServerExperienceRenderer } from '@contentful/experiences-svelte';
  import { experienceConfig } from '$lib/experience-config';

  let { data } = $props();
</script>

<ServerExperienceRenderer experience={data.experience} config={experienceConfig} />
```

#### Angular

Fetch in your Express server and pass the experience to the app as request context:

```ts
// server.ts
import { fetchExperience } from '@contentful/experiences-angular';
import { experienceConfig } from './app/lib/experience-config';

const experience = await fetchExperience(
  { spaceId, environmentId: 'master', experienceId: slug },
  { accessToken: CDA_TOKEN },
  { config: experienceConfig }
);

const response = await angularApp.handle(req, { experience });
```

```ts
// pages/experience-page.component.ts
@Component({
  imports: [ServerExperienceRendererComponent],
  template: `<cf-server-experience [experience]="experience" [config]="config" />`,
})
export class ExperiencePageComponent {
  protected readonly experience = inject(ExperienceStore).data?.experience ?? null;
  protected readonly config = experienceConfig;
}
```

`ExperienceStore` is a service in your app that reads the request context; the Angular example includes one. The resolved experience is plain JSON, so you can relay it to the browser with `TransferState` for hydration. See the [Angular example](https://github.com/contentful/experiences/tree/main/examples/angular) for the complete wiring.

## Renderer props

Both renderers take the same props. Only `experience` and `config` are required. The others override values the experience already carries.

| Prop            | Description                                                                                                                                              |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `experience`    | The experience from `fetchExperience` or `resolveExperience`. An experience with no nodes renders nothing.                                               |
| `config`        | The same configuration you passed to `fetchExperience`.                                                                                                  |
| `metadata`      | Merged over the experience's `metadata`.                                                                                                                 |
| `debug`         | Overrides the experience's `debug`. `false` turns debug mode off for an experience fetched with it on.                                                   |
| `renderUnknown` | The fallback for an unregistered component. See [Errors and debugging](/developers/docs/experience-orchestration/experiences-sdk/errors-and-debugging#custom-fallbacks). |
| `renderError`   | The fallback for a component that throws while rendering.                                                                                                |

`config` is a prop, not part of the experience, because it holds component references and functions. The experience stays serializable so it can cross the server/client boundary: React Server Components, SvelteKit page data, and Angular's `TransferState`.

## Re-render with a new experience

To show different content without a page load, fetch the experience again and pass it to the client renderer. For example, a client component that refetches an experience with new parameters:

```tsx
'use client';

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

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

export function FeaturedPosts({ initialExperience }: { initialExperience: PortableRenderPlan }) {
  const [experience, setExperience] = useState(initialExperience);

  async function showMore() {
    const nextExperience = await fetchExperience(
      {
        spaceId: '<space-id>',
        environmentId: 'master',
        experienceId: '<experience-id>',
        parameters: { featuredPosts: { limit: 12, order: 'sys.createdAt' } },
      },
      { accessToken: '<browser-safe-delivery-token>' },
      { config: experienceConfig }
    );
    setExperience(nextExperience);
  }

  return (
    <>
      <button onClick={showMore}>Show 12, oldest first</button>
      <ClientExperienceRenderer experience={experience} config={experienceConfig} />
    </>
  );
}
```

A browser fetch exposes its token. Use a token that's safe to expose, or a trusted proxy as `host`.

## Read the render context

Every component and every `resolveData` function sees the render context:

| Field      | Description                                                                          |
| ---------- | ------------------------------------------------------------------------------------ |
| `debug`    | Whether debug mode is on. Use it to add your own debug output.                       |
| `metadata` | The application data passed to `fetchExperience` and the renderer's `metadata` prop. |

Read it with `useExperience()` in React, `getExperience()` in Svelte, or `injectExperience()` in Angular. In `resolveData`, it's `ctx.experience`. The accessor throws outside a renderer.

`metadata` is the place for your application's own data, such as the locale, the slug, or feature flags. The SDK never writes to it. It's typed `Record<string, unknown>`, so narrow it where you read it:

```ts
interface PageMeta {
  slug: string;
  locale: string;
}

const { slug, locale } = useExperience().metadata as PageMeta;
```

`metadata` must be plain serializable data. Functions and class instances don't survive the server/client boundary.

## Render in other frameworks

* **React-based frameworks that need their own render walk**, such as Astro islands: see [Render experiences in Astro](/developers/docs/experience-orchestration/experiences-sdk/astro).
* **Frameworks without an adapter**, such as Vue or Solid: see [Other frameworks](/developers/docs/experience-orchestration/experiences-sdk/other-frameworks).