Using Experiences with Next.js
Table of contents
Using app router
Server-side rendering using app router
When using app router in Next.js, you can render your experience on the server.
Important:
- Use the vanilla JS fetchers (
fetchBySlug
orfetchById
) inside the server component instead of the React hooksuseFetchBySlug
oruseFetchById
. - The
experience
object returned by the fetchers is not serializable by Next.js directly due to their strict JSON serialization techniques. Therefore, we need to serialize theexperience
ourselves and pass the JSON string as a prop to the component. - Note: You need to register your components, design tokens, and custom breakpoints on both the server and client side.
Below is an example page using Next.js SSR in app router:
src/studio-config.ts
import {
defineBreakpoints,
defineComponents,
defineDesignTokens,
} from '@contentful/experiences-sdk-react';
//Configure your components, design tokens, and custom breakpoints here
defineComponents([
// Add your custom components here
// example:
// {
// component: Button,
// definition: {
// id: 'custom-button',
// name: 'Button',
// category: 'Custom Components',
// variables: {
// text: {
// displayName: 'Text',
// type: 'Text',
// defaultValue: 'Click me'
// },
// },
// },
// },
]);
defineBreakpoints([
// Add your custom breakpoints here
// example:
// {
// id: 'test-desktop',
// query: '*',
// displayName: 'All Sizes',
// displayIcon: 'desktop',
// previewSize: '100%',
// },
// {
// id: 'test-tablet',
// query: '<982px',
// displayName: 'Tablet',
// displayIcon: 'tablet',
// previewSize: '820px',
// },
// {
// id: 'test-mobile',
// query: '<576px',
// displayName: 'Mobile',
// displayIcon: 'mobile',
// previewSize: '390px',
// },
]);
defineDesignTokens({
// Add your design tokens here
// example:
// spacing: { XS: '4px', S: '16px', M: '32px', L: '64px', XL: '128px' },
// sizing: { XS: '16px', S: '100px', M: '300px', L: '600px', XL: '1024px' },
// color: {
// Slate: '#94a3b8',
// Azure: 'azure',
// Orange: '#fdba74',
// Blue: '#0000ff',
// },
// textColor: { Dark: '#1a1a1a', Light: '#efefef', Slate: '#94a3b8' },
});
src/app/[locale]/[slug]/pages.tsx
import Experience from '@/components/Experience';
import { createClient } from 'contentful';
import {
detachExperienceStyles,
fetchBySlug,
} from '@contentful/experiences-sdk-react';
//import the studio config server side
import '@/studio-config';
const accessToken = process.env.NEXT_PUBLIC_CTFL_ACCESS_TOKEN!;
const space = process.env.NEXT_PUBLIC_CTFL_SPACE!;
const environment = process.env.NEXT_PUBLIC_CTFL_ENVIRONMENT!;
const experienceTypeId = process.env.NEXT_PUBLIC_CTFL_EXPERIENCE_TYPE!;
const client = createClient({
space,
environment,
accessToken,
});
async function AppPage({
params,
}: {
params: { slug: string; locale: string };
}) {
const { locale = 'en-US', slug = 'home-page' } = params || {};
const experience = await fetchBySlug({
client,
slug,
experienceTypeId,
localeCode: locale,
});
// 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;
src/components/Experience.tsx
'use client';
import { ExperienceRoot } from '@contentful/experiences-sdk-react';
import React from 'react';
//import the studio config client side
import '@/studio-config';
interface ExperienceProps {
experienceJSON: string | null;
locale: string;
}
const Experience: React.FC<ExperienceProps> = ({ experienceJSON, locale }) => {
return <ExperienceRoot experience={experienceJSON} locale={locale} />;
};
export default Experience;
Note: The
ExperienceRoot
component is a client component. While it can be initially rendered on the server, on the client side it will hydrate and continue rendering on the client.
To see more detailed info on how to use Experiences with Next.js App Router, check out the Next.js App Router example.
Client-side rendering using app router
When using client-side rendering, fetching and displaying an experience is similar to how it is done in a normal single page application, except you add the 'use client' directive at the top of the component:
'use client';
import React from 'react';
import { createClient } from 'contentful';
import {
ExperienceRoot,
useFetchBySlug,
} from '@contentful/experiences-sdk-react';
const accessToken = process.env.NEXT_PUBLIC_CTFL_ACCESS_TOKEN!;
const space = process.env.NEXT_PUBLIC_CTFL_SPACE!;
const environment = process.env.NEXT_PUBLIC_CTFL_ENVIRONMENT!;
const experienceTypeId = process.env.NEXT_PUBLIC_CTFL_EXPERIENCE_TYPE!;
const localeCode = 'en-US';
const client = createClient({
space,
environment,
accessToken,
});
const MyComponent: React.FC = (props) => {
const { experience, isLoading, error } = useFetchBySlug({
client,
slug: 'homePage', //Could be fetched from the url,
experienceTypeId: experienceTypeId,
localeCode,
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <ExperienceRoot experience={experience} locale={localeCode} />;
};
export default MyComponent;
Using pages router
Server-side rendering using pages router
When using pages router in Next.js, you can render your experience on the server.
Important:
- Use the vanilla JS fetchers (
fetchBySlug
orfetchById
) inside of the server side methods (getServerSideProps
orgetStaticProps
) instead of the React hooksuseFetchBySlug
oruseFetchById
. - The
experience
object returned by the fetchers is not serializable by Next.js directly due to their strict JSON serialization techniques. Therefore, we need to serialize theexperience
ourselves and pass the JSON string as a prop to the component.
Below is an example page using Next.js SSR in pages router:
import { createClient } from 'contentful';
import {
ExperienceRoot,
defineComponents,
detachExperienceStyles,
fetchBySlug,
} from '@contentful/experiences-sdk-react';
import { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';
import Head from 'next/head';
defineComponents([
// Add your custom components here
// example:
// {
// component: Button,
// definition: {
// id: 'custom-button',
// name: 'Button',
// category: 'Custom Components',
// variables: {
// text: {
// displayName: 'Text',
// type: 'Text',
// defaultValue: 'Click me'
// },
// },
// },
// },
]);
const accessToken = process.env.NEXT_PUBLIC_CTFL_ACCESS_TOKEN!;
const space = process.env.NEXT_PUBLIC_CTFL_SPACE!;
const environment = process.env.NEXT_PUBLIC_CTFL_ENVIRONMENT!;
const experienceTypeId = process.env.NEXT_PUBLIC_CTFL_EXPERIENCE_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,
locale = 'en-US',
}: GetServerSidePropsContext<{ slug: string }>) => {
const { slug = 'home-page' } = params || {};
const experience = await fetchBySlug({
client,
slug,
experienceTypeId,
localeCode: locale,
});
// 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;
To see more detailed info on how to use Experiences with Next.js Pages Router, check out the Next.js Pages Router.
Client-side rendering using pages router
When using client side rendering, fetching and displaying an experience is similar to how it is done in a normal single page application.
import React from 'react';
import { createClient } from 'contentful';
import {
ExperienceRoot,
defineComponents,
useFetchBySlug,
} from '@contentful/experiences-sdk-react';
const accessToken = process.env.NEXT_PUBLIC_CTFL_ACCESS_TOKEN!;
const space = process.env.NEXT_PUBLIC_CTFL_SPACE!;
const environment = process.env.NEXT_PUBLIC_CTFL_ENVIRONMENT!;
const experienceTypeId = process.env.NEXT_PUBLIC_CTFL_EXPERIENCE_TYPE!;
const localeCode = 'en-US';
defineComponents([
// Add your custom components here
// example:
// {
// component: Button,
// definition: {
// id: 'custom-button',
// name: 'Button',
// category: 'Custom Components',
// variables: {
// text: {
// displayName: 'Text',
// type: 'Text',
// defaultValue: 'Click me'
// },
// },
// },
// },
]);
const client = createClient({
space,
environment,
accessToken,
});
const Experience: React.FC = (props) => {
const { experience, isLoading, error } = useFetchBySlug({
client,
slug: 'homePage', //Could be fetched from the url,
experienceTypeId: experienceTypeId,
localeCode,
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <ExperienceRoot experience={experience} locale={localeCode} />;
};
export default Experience;