Set up Experiences SDK with Next.js
Prerequisites
Please read through the installation guide 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
This guide walks you through on setting up Studio Experiences with your Next.js app using app router.
Studio Experiences does not currently support registering React 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:
// 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:
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={{ width: '100%' }}>
{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
andspace
- You can find the token and space values in your Contentful space API keys settings, which you can get to either by pressingCmd/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 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:
// 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:
// 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 to defineComponents
. This definition includes:
id
- used to identify the componentname
- the name of the component as it appears in Experiencescategory
- the category the component will be listed under in Experiencesvariables
- 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 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:
// 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:
// 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:
// 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={{ width: '100%' }}>
<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
andspace
- You can find the token and space values in your Contentful space API keys settings, which you can get to either by pressingCmd/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:
// 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:
// 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 to defineComponents
. This definition includes:
id
- used to identify the componentname
- the name of the component as it appears in Studiocategory
- the category the component will be listed under in Studiovariables
- 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 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:
// 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.