Migrating to the Experience Model

Migrating from the Ninetailed Legacy Personalization model to the new Experience model.

This guide is for Ninetailed users who are using an older version of the Contentful Ninetailed integration. New Ninetailed users and connections should configure new Contentful connections to use the default content model that supports both personalization and experimentation (as opposed to the optionally toggleable "Legacy Model", which supports only personalizations). Hygraph integrations do not support experiments at this time.

Introduction

Prior versions of:

  1. the content model that Ninetailed installed in Contentful, and

  2. the pre-built components in the React (and Next.js) SDKs

allowed users to create and serve personalizations. Ninetailed has expanded both the Contentful content model and the SDKs to additionally support creating and serving experiments. This guide is for Ninetailed users whose applications support only personalizations to upgrade to also be able to create and serve experiments.

Prerequisites

  1. Have an existing connection to a Contentful space environment configured from a Ninetailed dashboard

  2. Have at least one free content type in the Contentful space environment

  3. Have a copy of your production Contentful environment to perform these steps in isolation from your production application. Consult Contentful's Environments and environment aliases best practices documentation for more information.

Step-by-Step

1. Upgrade your Ninetailed SDK dependency to at least the 2.x version. We recommend using the latest published major version whenever possible:

npm install @ninetailed/experience.js-react@latest

Or, if using the Next.js SDK:

@ninetailed/experience.js-next@latest

2. Disable the Legacy Model in the connection to your non-production Contetnful environment from with the Ninetailed dashboard by clicking "Edit" in the top right and toggling off the "Legacy Model". Click "Save" to finalize changes. Doing so will automatically:

  • Create a new content type "Ninetailed Experience" (nt_experience), and

  • Create a new field on each experiences-enabled content type (nt_experiences)

  • Disable the nt_variants and nt_audience fields from being edited on content types that were previously personalizable.

This changes your content model such that baseline entries of personalizable content types now reference Ninetailed Experience entries instead of directly referencing an audience and its variants. Ninetailed Experiences entries now take on the responsibility of referencing an audience and variants. Ninetailed Experiences can be either personalizations or experiments, which are distinguished by the read-only nt_type field.

3. If using the React or Next.js SDK, provide all experiments to the <NinetailedProvider>. This requires that you fetch all entries of type nt_experience whose nt_type field is equal to nt_experiment. Provide the array of returned entry objects as the value to the experiments prop.

This step is not required if using the Gatsby SDK.

// utils/api.js
import { createClient } from 'contentful';

const contentfulClient = createClient({
  space: process.env.CONTENTFUL_SPACE_ID ?? '',
  accessToken: process.env.CONTENTFUL_TOKEN ?? '',
  environment: process.env.CONTENTFUL_ENVIRONMENT ?? 'master',
});

export async function getExperiments() {
  const query = {
    content_type: 'nt_experience',
    'fields.nt_type': 'nt_experiment',
  };
  const client = getClient(false);
  const entries = await contentfulClient.getEntries(query);
  return experiments = entries.items;
}
// pages/[[...slug]].jsx
...
import { GetStaticProps } from 'next';
import { getExperiments } from '@/utils/api'
...
export const getStaticProps = async ({ params, preview }) => {
  ...
  const page = await ...
  const experiments = await getExperiments(),
  return {
    props: { page, ninetailed: { experiments } },
  };
};
```
// pages/_app.jsx
import {
  NinetailedProvider,
} from '@ninetailed/experience.js-next';

const myApp = ({ Component, pageProps }) => {
  return (
    ...
    <NinetailedProvider
      ...
      clientId={process.env.NEXT_PUBLIC_NINETAILED_CLIENT_ID ?? ''}
      environment={process.env.NEXT_PUBLIC_NINETAILED_ENVIRONMENT ?? 'main'}
      experiments={pageProps.ninetailed?.experiments || []}
    >
      <Component {...pageProps} />
    </NinetailedProvider>
  )
}

export default myApp;

4. Replace the <Personalize> component provided by your React-based SDK with the <Experience> component. Pass the Ninetailed Experiences that the entry is referencing as the experiences prop, rather than the formerly attached variants . Additionally, pass any custom props to the passthroughProps prop. Choose whether to import the ExperienceMapper class to map your experiences to the correct format based on whether you are retrieving experiments in the format provided by Contentful's REST API or another format.

Your current implementation of the <Personalize>a component likely looks something like the following:

// Your current <Personalize> implementation
// components/MyComponent.jsx
import { Personalize } from '@ninetailed/experience.js-react'; // or @ninetailed/experience.js-next.js or @ninetailed/experience.js-gatsby
...
const mappedVariants = (myEntry.fields.nt_variants || []).map(yourMapVariantsFunction)
...
return (
  ...
    <Personalize
      ...
      variants={mappedVariants}
      customProp={myValue}
    />
  </div>
)

If you're retrieving content from a Contentful RESTful API (CDA or CPA), you can use the ExperienceMapper class methods from our @ninetailed/experience.js-utils-contentful package to easily map experiences to the correct format.

// <Experience> component with Contentful REST APIs
// components/MyComponent.jsx
import { Experience } from '@ninetailed/experience.js-react'; // or @ninetailed/experience.js-next.js
import { ExperienceMapper } from '@ninetailed/experience.js-utils-contentful'
...
const mappedExperiences = (myEntry.fields.nt_experiences || [])
  .filter((experience) => ExperienceMapper.isExperienceEntry(experience))
  .map((experience) => ExperienceMapper.mapExperience(experience));
...
return (
  ...
    <Experience
      ...
      experiences={mappedExperiences}
      passthroughProps={{
        customProp: myValue
      }}
    />
  </div>
)

If you're retrieving content using a different API (for example, the Gatsby GraphQL API, the Contenful GraphQL API, or a custom API your organization created), you can instead use the ExperienceMapper class methods from our @ninetailed/experience.js-utils to perform the required mapping first transforming your experience content into the correct format.

// <Experience> component with other APIs
// components/MyComponent.tsx
import { Experience } from '@ninetailed/experience.js-react'; // or @ninetailed/experience.js-next.js, or @ninetailed/experience.js-gatsby 
import { ExperienceMapper } from '@ninetailed/experience.js-utils'
...
const mappedExperiences = (myEntry.nt_experiences || [])
  .map((experience) => {
    return {
      id: experience.id,
      name: experience.name
      type: experience.nt_type as 'nt_personalization' | 'nt_experiment'
      config: experience.nt_config,
      audience: {
        id: experience.nt_audience.nt_audience_id
      },
      variants: experience.varaints.map((variant) => {
        return {
          ...variant,
          id: variant.id
        }
      })
    }
  })
  .filter((experience) => ExperienceMapper.isExperienceEntry(experience))
  .map((experience) => ExperienceMapper.mapExperience(experience));
...
return (
  ...
    <Experience
      ...
      experiences={mappedExperiences}
      passthroughProps={{
        customProp: myValue
      }}
    />
  </div>
)

5. 🎉 Bask in the glory of being able to create experiments from within your CMS! Test out creating experimentations, and when ready, make these changes in your production application.

Last updated