Was this page helpful?

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 or fetchById) inside the server component instead of the React hooks useFetchBySlug or useFetchById.
  • 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 the experience 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 or fetchById) inside of the server side methods (getServerSideProps or getStaticProps) instead of the React hooks useFetchBySlug or useFetchById.
  • 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 the experience 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;