Was this page helpful?

Using Experiences with NextJS

Table of contents

Using pages router

Server-side rendering using pages router

When using pages router in NextJS, 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 NextJS 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.
  • In the component, we need to recreate the experience object from the JSON string by passing it to the createExperience function.

Below is an example page using NextJS SSR in the pages router:

import { createClient } from 'contentful';
import {
  fetchBySlug,
  ExperienceRoot,
  createExperience,
} from '@contentful/experiences-sdk-react';
import { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';

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,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  //Recreate the experience object from the serialized JSON
  const experience = createExperience(experienceJSON);

  return (
    <main style={{ width: '100%' }}>
      <ExperienceRoot experience={experience} locale={'en-US'} />
    </main>
  );
}

export const getServerSideProps = async ({}: GetServerSidePropsContext) => {
  const experience = await fetchBySlug({
    client,
    slug: 'homePage', //could be fetched from the context
    experienceTypeId,
    localeCode,
  });

  //Serialize the experience manually
  const experienceJSON = JSON.stringify(experience);

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

export default MyPage;

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,
  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 app router

Server-side rendering using app router

Using the app router in Next.js, data is fetched in a server component for efficient and secure handling and passed to a client component to render the experience. This way we ensure the benefits of SSR while enabling client-side interactivity.

Important:

  • Use the vanilla JS fetchers (fetchBySlug or fetchById) inside the server component instead of the React hooks useFetchBySlug or useFetchById.
  • fetchBySlug or fetchById must be imported from @contentful/experiences-core and not from @contentful/experiences-sdk-react to avoid issues with the server-side rendering.

Server component

Fetch an experience in a server component.

app/[lang]/[slug]/pages.tsx

import { cache } from 'react';
import { createClient } from 'contentful';
import { ExperienceAppRouter } from '@/app/ExperienceAppRouter';
// Note: import from experiences-core
import { fetchBySlug } from '@contentful/experiences-core';

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!;
// example experience type ID
const experienceTypeId = process.env.NEXT_PUBLIC_CTFL_EXPERIENCE_TYPE!;

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

export const fetchData = cache(
  async ({ slug, locale }: { slug: string; locale: string }) => {
    const currentLocale = locale;

    const experience = await fetchBySlug({
      client,
      slug: slug as string,
      experienceTypeId,
      localeCode: currentLocale,
    });

    if (!experience) {
      throw new Error('Experience not found');
    }

    const experienceString = JSON.stringify(experience);

    return {
      experienceString,
    };
  }
);

async function AppPage({ params }: { params: { slug: string; lang: string } }) {
  const { experienceString } = await fetchData({
    slug: params.slug,
    locale: params.lang,
  });

  return (
    <ExperienceAppRouter
      experienceString={experienceString}
      locale={params.lang}
    />
  );
}

export default AppPage;

Client component

Render your experience in a client component.

app/ExperienceAppRouter.tsx

'use client';
import React, { useMemo } from 'react';
import {
  ExperienceRoot,
  createExperience,
  detachExperienceStyles,
} from '@contentful/experiences-sdk-react';

// Here you can register your components with Experiences SDK
export const ExperienceAppRouter = ({
  experienceString,
  locale,
}: {
  experienceString: string;
  locale: string;
}) => {
  const experience = useMemo(() => {
    return createExperience(experienceString);
  }, [experienceString]);

  const styles = useMemo(() => {
    return detachExperienceStyles(experience);
  }, [experience]);

  return (
    <main style={{ width: '100%' }}>
      <style>{styles}</style>
      <ExperienceRoot experience={experience} locale={locale} />
    </main>
  );
};

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;