> This page is for Studio.

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

# Set up Experiences SDK with Next.js

> Learn how to install the Experiences SDK in Next.js project.

## Prerequisites

Please read through the [installation guide](/installation) to ensure you meet the prerequisites and have the Experiences SDK installed in your project before starting.

Then, follow the steps below for the type of Next.js project you are using:

* [Setting up a Next.js project with app router](#setting-up-a-nextjs-project-with-app-router)
* [Setting up a Next.js project with pages router](#setting-up-a-nextjs-project-with-pages-router)

## Setting up a Next.js project with app router

This guide walks you through on setting up Studio Experiences with your Next.js app using app router.

> **Info**
>
> Studio Experiences does not currently support registering [React Server Components](https://react.dev/reference/rsc/server-components) as Studio components. Studio components must be client components, marked with the 'use client' directive. However, client components can be initially rendered on the server, allowing you to do server-side rendering. We are currently investigating adding support for server components.

### Rendering an experience

Experiences are rendered with the `ExperienceRoot` component. Currently, they must be rendered in a client component when using Next.js app router. Let's create a new folder at **src/components**, in that folder a new component file named **Experience.tsx**, and add the following:

```ts
// src/components/Experience.tsx
'use client';
import { ExperienceRoot } from '@contentful/experiences-sdk-react';
import React from 'react';

interface ExperienceProps {
  experienceJSON: string | null;
  locale: string;
}

const Experience: React.FC<ExperienceProps> = ({ experienceJSON, locale }) => {
  return <ExperienceRoot experience={experienceJSON} locale={locale} />;
};

export default Experience;
```

The `Experience` component is a client wrapper (using the 'use client' directive) for the `ExperienceRoot` component. The `experienceJSON` and `locale` props are passed through to the component.

Next, we render the `Experience` component from a page in our Next.js app. Create a file at **src/app/\[slug]/pages.tsx** and add the following code:

```ts
import { createClient } from 'contentful';
import {
  detachExperienceStyles,
  fetchBySlug,
} from '@contentful/experiences-sdk-react';
import Experience from '../../../components/Experience';

const accessToken = 'your_access_token';
const space = 'your_space_id';
const environment = 'your_contentful_environment';
const experienceTypeId = 'id_of_experience_content_type';

const client = createClient({
  space,
  environment,
  accessToken,
});

type Props = {
  params: Promise<{ slug: string }>;
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};

async function AppPage({ params, searchParams }: Props) {
  const { slug } = await params;
  const locale = 'en-US';
  const { expEditorMode } = await searchParams;

  const experience = await fetchBySlug({
    client,
    slug,
    experienceTypeId,
    localeCode: locale,
    isEditorMode: expEditorMode === 'true',
  });

  // extract the styles from the experience
  const stylesheet = experience ? detachExperienceStyles(experience) : null;

  // experience currently needs to be stringified manually to be passed to the component
  const experienceJSON = experience ? JSON.stringify(experience) : null;

  return (
    <main style=&#123;&#123; width: '100%' &#125;&#125;>
      {stylesheet && <style>{`{stylesheet}`}</style>}
      <Experience experienceJSON={experienceJSON} locale={locale} />
    </main>
  );
}

export default AppPage;
```

There is a lot happening in this file so let's break it down.

First, we set some values needed to create a Contentful client object in order to make requests to the Contentful API. We suggest not storing these values in your source code but using environment variables to pull them in at build time.

* `accessToken` and `space` - You can find the token and space values in your Contentful space API keys settings, which you can get to either by pressing `Cmd/Ctrl + 'k'` and typing 'API keys' or by navigating to the settings in the Contentful app.
* `experienceTypeId` - This is the id of the content model set up for experiences. Open the content model in the Contentful app and click the "Copy ID" button to get the id.
* `environment` - This is the environment you are using in Contentful. You can find this in the Contentful app by clicking on the settings and going to environments.

When fetching an experience, you pass in a `slug` (that uniquely identifies the experience) and a `locale` (to fetch the experience in the correct language). We collect the `slug` from the URL params, and hard-code the `locale` to 'en-US', though we could also pass it in dynamically.

The search params might contain a query param named `expEditorMode` to signify that the page is being loaded in Studio's editor. We pass this value to the `fetchBySlug` method to help determine if the experience should be fetched in editor mode. While in editor mode, the experience is not fetched at this time, but is instead passed into the `ExperienceRoot` component dynamically during editing.

With all the needed information to make a fetch request, we can now do so by calling the `fetchBySlug` method.

Once we have the experience, we extract the CSS values from the experience by calling the `detachExperienceStyles` method. This will give us a stylesheet representing all the design value options that are configured. We write out this stylesheet to a "style" tag in the rendered JSX.

Last, we serialize the experience object to JSON, because the experience object is not serializable directly by Next.js due to their strict JSON serialization techniques. Once that is done, we render the `Experience` component we created earlier and pass in the `experienceJSON` and `locale` params as props.

Everything is now set up in your app to render an experience. Launch your app in your local development server, log into the Contentful portal, sure a content preview URL is configured to point to your local web development server and create a new experience.

To see more detailed info on how to use Experiences with Next.js App Router, check out the [Next.js App Router](https://github.com/contentful/experience-builder/tree/development/examples/next-app-router) example.

### Using custom components

We provide several basic components out of the box, but you can also use your own custom components. To demonstrate how to use custom components with Experiences and Next.js, we will create a simple button component that is configurable from Studio.

Create a new file at **src/components/Button.tsx** and add the following:

```ts
// src/components/Button.tsx
'use client';
import React from 'react';

interface ButtonComponentProps {
  text: string;
}

export const Button: React.FC<ButtonComponentProps> = ({ text, ...props }) => {
  return <button {...props}>{text}</button>;
};
```

Note that the `Button` component is marked with the 'use client' directive, which tells React that this component is a client-side component. This is required for all components that are used in Studio Experiences.

Next, register your component with the Experiences SDK. This is done with the help of the `defineComponents` function which is imported from the `@contentful/experiences-sdk-react` package. We suggest creating a specific file for handling all your component registration and setup. Create a file at **src/studio-config.ts** and add the following:

```ts
// src/studio-config.ts
import { defineComponents } from '@contentful/experiences-sdk-react';
import { Button } from './components/Button';

defineComponents([
  {
    component: Button,
    definition: {
      id: 'custom-button',
      name: 'Button',
      category: 'Custom Components',
      variables: {
        text: {
          displayName: 'Text',
          type: 'Text',
          defaultValue: 'Click me',
        },
      },
    },
  },
]);
```

Above, we import the `Button` component and register it with Experiences by providing a [component definition](/component-definition-schema) to `defineComponents`. This definition includes:

* `id` - used to identify the component
* `name` - the name of the component as it appears in Experiences
* `category` - the category the component will be listed under in Experiences
* `variables` - the variables that can be configured in Experiences

The most interesting part of the configuration here are the variables, in which we configure one named 'text'. This variable is of type 'Text' and has a default value of 'Click me'. This variable is configurable in Experiences and will be passed to the `Button` component as a prop.

To learn more about how you can configure your components, check out the [Register custom components](/register-custom-components) guide.

Since our Next.js app is running components on both the server-side and client-side, we need to make sure this config file is imported in both places.

To import the `studio-config.ts` file in server-side, we suggest adding it to a server component that is rendered from the **app** directory. We can use the page we set up earlier, **src/app/\[slug]/pages.tsx**. Add the following import at the top of the file:

```ts
// src/app/[slug]/pages.tsx
//import the studio config server-side
import '../../../studio-config';
```

For client-side, we suggest importing the `studio-config.ts` file in the **src/components/Experience.tsx** file. Add the following import at the top of the file:

```ts
// src/components/Experience.tsx
//import the studio config client-side
import '../../../studio-config';
```

With the `Button` component registered, you can now use it in Studio. Open an experience and add the `Button` component by dragging your button from the 'Custom Components' section. You should see the 'Text' variable in the component content panel on the right, where you can change the text that appears on the button.

## Setting up a Next.js project with pages router

This guide walks you through on setting up Studio Experiences with Next.js app using pages router.

### Rendering an experience

Experiences are rendered with the `ExperienceRoot` component, which we will set up in a Next.js page. Create a new file at **src/pages/\[slug].tsx** and add the following:

```ts
// src/pages/[slug].tsx
import { createClient } from 'contentful';
import {
  ExperienceRoot,
  detachExperienceStyles,
  fetchBySlug,
} from '@contentful/experiences-sdk-react';
import { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';
import Head from 'next/head';

const accessToken = 'your_access_token';
const space = 'your_space_id';
const environment = 'your_contentful_environment';
const experienceTypeId = 'id_of_experience_content_type';
const localeCode = 'en-US';

const client = createClient({
  space,
  environment,
  accessToken,
});

function MyPage({
  experienceJSON,
  stylesheet,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  return (
    <>
      {stylesheet && (
        <Head>
          <style data-ssg>{`{stylesheet}`}</style>
        </Head>
      )}
      <main style=&#123;&#123; width: '100%' &#125;&#125;>
        <ExperienceRoot experience={experienceJSON} locale={localeCode} />
      </main>
    </>
  );
}

export const getServerSideProps = async ({
  params,
  query,
}: GetServerSidePropsContext<{ slug: string }, { expEditorMode?: string }>) => {
  const { slug = 'home-page' } = params || {};
  const { expEditorMode } = query;
  const locale = 'en-US';
  const experience = await fetchBySlug({
    client,
    slug,
    experienceTypeId,
    localeCode: locale,
    isEditorMode: expEditorMode === 'true',
  });

  // extract the styles from the experience
  const stylesheet = experience ? detachExperienceStyles(experience) : null;

  // experience currently needs to be stringified manually to be passed to the component
  const experienceJSON = experience ? JSON.stringify(experience) : null;

  return {
    props: {
      experienceJSON,
      stylesheet,
    },
  };
};

export default MyPage;
```

There is a lot happening in this file so let's break it down.

First, we set some values needed to create a Contentful client object in order to make requests to the Contentful API. We suggest not storing these values in your source code but using environment variables to pull them in at build time.

The variables can be found in the Contentful app in the following places:

* `accessToken` and `space` - You can find the token and space values in your Contentful space API keys settings, which you can get to either by pressing `Cmd/Ctrl + 'k'` and typing 'API keys' or by navigating to the settings in the Contentful app.
* `experienceTypeId` - This is the id of the content model set up for experiences. Open the content model in the Contentful app and click the "Copy ID" button to get the id.
* `environment` - This is the environment you are using in Contentful. You can find this in the Contentful app by clicking on the settings and going to environments.

The `MyPage` component receives the props `experienceJSON`, `locale`, and `stylesheet` from the `getServerSideProps` method. The `stylesheet` is written out to a "style" tag in the rendered JSX, and the `experienceJSON` and `locale` are passed to the `ExperienceRoot` component.

The `getServerSideProps` method is used to fetch the experience and stylesheet server-side and pass it to the `MyPage` component as props.

When fetching an experience, you pass in a `slug` (that uniquely identifies the experience) and a `locale` (to fetch the experience in the correct language). We collect the `slug` from the URL params, and hard-code the `locale` to 'en-US', though we could also pass it in dynamically.

The search params might contain a query param named `expEditorMode` to signify that the page is being loaded in Studio's editor. We pass this value to the `fetchBySlug` method to help determine if the experience should be fetched in editor mode. While in editor mode, the experience is not fetched at this time, but is instead passed into the `ExperienceRoot` component dynamically during editing.

With all the needed information to make a fetch request, we can now do so by calling the `fetchBySlug` method.

Once we have the experience, we extract the CSS values from the experience by calling the `detachExperienceStyles` method. This will give us a stylesheet representing all the design value options that are configured. We write out this stylesheet to a "style" tag in the rendered JSX.

Last, we serialize the experience object to JSON, because the experience object is not serializable directly by Next.js due to their strict JSON serialization techniques. Once that is done, we return all the data as props to the `MyPage` component.

Everything is now set up in your app to render an experience. Launch your app in your local development server, log into the Contentful portal, ensure a content preview URL is configured to point to your local web development server and create a new experience.

### Using custom components

We provide several basic components out of the box, but you can also use your own custom components. To demonstrate how to use custom components with Experiences and Next.js, we will create a simple button component that is configurable from Studio.

Create a new file at **src/components/Button.tsx** and add the following:

```ts
// src/components/Button.tsx
import React from 'react';

interface ButtonComponentProps {
  text: string;
}

export const Button: React.FC<ButtonComponentProps> = ({ text, ...props }) => {
  return <button {...props}>{text}</button>;
};
```

Next, your component needs to be registered with the Experiences SDK. This is done with the help of the `defineComponents` function which is imported from the `@contentful/experiences-sdk-react` package. We suggest creating a specific file for handling all your component registration and setup. Create a file at **src/studio-config.ts** and add the following:

```ts
// src/studio-config.ts
import { defineComponents } from '@contentful/experiences-sdk-react';
import { Button } from './components/Button';

defineComponents([
  {
    component: Button,
    definition: {
      id: 'custom-button',
      name: 'Button',
      category: 'Custom Components',
      variables: {
        text: {
          displayName: 'Text',
          type: 'Text',
          defaultValue: 'Click me',
        },
      },
    },
  },
]);
```

Above, we import the `Button` component and register it with Studio by providing a [component definition](/component-definition-schema) to `defineComponents`. This definition includes:

* `id` - used to identify the component
* `name` - the name of the component as it appears in Studio
* `category` - the category the component will be listed under in Studio
* `variables` - the variables that can be configured in Studio

The most interesting part of the config here is the variables, in which we configure one named 'text'. This variable is of type 'Text' and has a default value of 'Click me'. This variable will be configurable in Studio and will be passed to the `Button` component as a prop.

There is a lot more you can configure for your components, but for now, we will keep it simple. For more information, check out the [Register Custom Components](/register-custom-components) guide.

To import the `studio-config.ts` file, we suggest adding it to a component that is rendered from the **pages** directory. We can use the page we set up earlier, **src/pages/\[slug].tsx**. Add the following import at the top of the file:

```ts
// src/pages/[slug].tsx
import '../../../studio-config';
```

With the `Button` component registered, you can now use it in Studio. Open an experience and add the `Button` component by dragging your button from the 'Custom Components' section. You should see the 'Text' variable in the component content panel on the right, where you can change the text that appears on the button.