Contentstack Next.js tutorial

This tutorial guides you through the process of adding personalization to the Contentstack Next.js starter app. By the end of this tutorial you will have a personalized Next.js application with content and personalization configured by content authors in Contentstack using the Uniform app from the Contentstack marketplace:

  • Content author can classify a blog post entry to describe what a visitor reading the post is likely to be interested in.
  • When a visitor views certain blog pages, the visitor's interests are identified.
  • Content author can create content for the hero component that targets visitor interests.
  • Content author can define the conditions that must be met in order for each hero component to be displayed.
  • When the visitor's interests are identified, the home page displays personalized content.

Before you get started, be sure you have the following:

  1. Administrator access to Contentstack - to add the Uniform app to your Contentstack organization.
  2. Vercel account - to deploy the Contentstack Next.js starter. You can get a free account here. You should also have a connection configured to a Git provider (GitHub, GitLab or Bitbucket). If you don't, you will be prompted to configure it during the setup process.
  3. Administrator access to Uniform - to configure the personalization settings that are available in Contentstack. If you don't already have a Uniform account, you can request an account at https://uniform.dev/try.
  4. Node.js (version 14 or greater) - installed on your machine to work with the Next.js app.
  5. Uniform Context browser extension - to facilitate development and testing personalization on the Next.js app. You can get the extension here. This isn't required to use Uniform Context, but it makes development and testing much easier.

This tutorial uses the NextJs Starter that's available on the Contentstack Marketplace. This starter provides everything you need to have a fully functional demo site built with Next.js:

  • Contentstack stack populated with content
  • Next.js application with components that use content from Contentstack
  • Source code repository to store the Next.js application
  • Vercel project that builds and deploys the Next.js application

tip

If you know how to install a starter from the Contentstack Marketplace, you can skip this section. Just be sure to add the NextJs Starter to your organization.

  1. Log into Contentstack.

  2. Navigate to Marketplace > Starters.

  3. Install the starter NextJs Starter.

  4. Authorize the starter to have access to your Contentstack organization.

  5. Enter the following value for the stack name:

    NextJs Starter
  6. Click Import Starter.

    About this step

    As the import process runs, you will see a log of its activity.

  7. Click Deploy to Vercel.

  8. A popup window appears. Click Create to create the repository.

    About this step

    This creates a repository with the source code for the starter application in your preferred Git provider.

  9. On the Add Integrations screen, click Add.

    About this step

    This adds the Contentstack integration to your Vercel project. Then Vercel starts to deploy the Next.js application. This can take a minute or two. The popup window will close when the deploy process is finished.

  10. Summary details are displayed that describe the results of adding the starter. You can find the following links:

    • Stack URL - Stack in Contentstack
    • Deployment URL - Deployed Next.js application
    • Repository URL - Source code repository for the Next.js application
  11. Click the Deployment URL.

    vercel-deployed

    About this step

    This will open a new tab with the deployed Next.js application, populated with content from the Contentstack stack.

  12. Click the Repository URL.

    About this step

    This will open a new tab with the source code repository for the Next.js application. Later in this tutorial you will add Uniform Context to this application.

  13. Back in Contentstack, click Open Stack.

In this example, personalization is based on the visitor's interests. Those interests are:

  • Defined in Uniform
  • Assigned in Contentstack
  • Used in the Next.js application to power personalization

In Uniform, a project is the basic unit of organization. A project is where you will define the criteria you want to use to configure personalization.

  1. Log into Uniform.

  2. On the Projects page, click (+) to create a new project.

    uniform-add-project
  3. Enter the following for the project name:

    NextJs Starter for Contentstack
  4. Scroll down and click Empty project.

    uniform-empty-project
  5. Click Continue.

  6. Your project is created and you are taken to the project home page.

    uniform-project-home-page

You must add the Uniform app from the Contentstack Marketplace to your stack.

tip

For instructions, see the guide on how to configure the Marketplace app.

info

Be sure to note the Uniform API key you create. You will need this value in a later step.

You must make some changes to the content types in your stack in order to allow content editors to add and configure personalization.

Blog Entry#

When a visitor views a blog entry, you have the opportunity to learn a little about what the visitor is interested in. In the NextJs Starter, the blog entries can be categorized by topic:

  • Architecture
  • Data
  • Futuristic

You must add a field to the content type Blog Entry to store the topic that's most relevant for each blog entry. The value for the topic will come from Uniform.

content-type-blog-entry-model
  1. In Contentstack, open the content type Blog Entry.

  2. Add a Custom field.

  3. Enter the following values:

    • Display Name: Topic
    • Unique ID: topic
  4. Click the dropdown Select Extension/App.

  5. Select Uniform Enrichment Tag.

    field-select-extension-enrichment
  6. Click Proceed.

  7. Enter the following for the field Help Text:

    The enrichments you select will help to identify the visitor's interests, which are used to create a personalized experience for the visitor.
  8. Click Save and Close.

Content authors need the ability to create entries for a hero banner that can be displayed to visitors who match certain criteria. A content type Hero is needed for these entries.

content-type-hero-model
  1. In Contentstack, create a new content type:

    • Name: Hero
    • UID: hero
    • Description: Content used in a banner that appears at the top of a landing page
    • Type: Multiple
    content-type-hero
  2. Click Save and proceed.

  3. Add a Single Line Text box field.

  4. Enter the following values:

    • Display Name: Message
    • Unique ID: message
  5. Click Save.

  6. Add a Custom field.

  7. Enter the following values:

    • Display Name: Personalization criteria
    • Unique ID: personalization_criteria
  8. Click the dropdown Select Extension/App.

  9. Select Uniform Personalization Criteria.

    field-select-extension-criteria
  10. Click Proceed.

  11. Enter the following for the field Help Text:

    When these conditions are met, this content will be used on the home page.
  12. Click Save and Close.

Content authors will have the ability to associate hero entries to a page entry. The content type Page must be updated to support this.

content-type-page-model
  1. In Contentstack, open the content type Page.

  2. Add a Reference field.

  3. In Basic settings, enter the following values:

    • Display Name: Personalized hero
    • Unique ID: personalized_hero
    • Referenced Content Type: Hero
  4. In Advanced settings, enter the following values:

    • Options: Multiple
  5. Click Save and Close.

You must update the following entries:

  • Blog Entry - Associate each entry with a topic.
  • Hero - Create the entries that will power the personalized banner on the home page.
  • Page - Associate the hero entries with the home page.

Uniform personalization depends on the Uniform tracker. This is a client-side component that captures visitor activity. When the visitor views a blog entry, you want the Uniform tracker to keep track of the topic associated with the blog entry. This enables you to understand the topics the visitor is interested in.

When you set the Topic field on a blog entry, you are setting instructions for the Uniform tracker.

  1. In Contentstack, open the entry Robotics - Changing Our Lives and Future.

  2. Scroll down to the field Topic.

    field-topic-no-enrichments-defined
  3. Click on the word here to open a new tab with the Uniform Enrichments page.

    uniform-no-enrichments
  4. Click (+) to create a new enrichment.

  5. Enter the following values:

    • Name: Topic
    • Public ID: 1
    uniform-add-enrichment
  6. Click Save.

    uniform-enrichment-added
  7. Click Add value.

  8. Enter the following values:

    • Name: Futuristic
    • Public ID: 1
    uniform-add-enrichment-value-futuristic
  9. Click Save.

    uniform-futuristic-value-added
  10. In Contentstack, refresh the entry page.

    field-topic-no-enrichments-selected
  11. Click on the word here.

    field-topic-enrichment-select
  12. Click Select > Enrichment: Futuristic.

    field-topic-enrichment-futuristic-selected
  13. Click Add.

    field-topic-enrichment-futuristic-added
  14. Click Save.

  15. In Uniform, add another value to the enrichment Topic:

    • Name: Architecture
    • Public ID: 2
    uniform-architecture-value-added
  16. In Uniform, add another value to the enrichment Topic:

    • Name: Data
    • Public ID: 3
    uniform-data-value-added
  17. In Contentstack, set the value on the field Topic on the other Blog Entry entries:

    TitleTopic
    The future of business with AIFuturistic
    Traditional vs. Decoupled vs. Headless CMS - Know the DifferenceArchitecture
    The modern Cloud EcosystemArchitecture
    Data Mining and its significance in Business AnalyticsData
    Headless CMS: The Solution to Top Challenges in EcommerceArchitecture
  18. Publish the entries to the environment development.

Hero entries provide the personalized content that will be displayed on the home page. The specific entry that's used when a visitor views the home page depends on the visitor's activity.

  1. In Contentstack, create a new entry using the content type Hero.

  2. Enter the following value for the field Title:

    Architecture Hero
  3. Enter the following value for the field Message:

    You like architecture!
  4. In the field Personalization criteria, click Add Criteria.

    field-topic-no-criteria-defined
  5. Click Select > Enrichment: Architecture.

    field-topic-criteria-architecture-select

    About this step

    By default, the criteria is set to match when the enrichment score is greater than 50. The enrichment score is set to 50 when a blog entry page is viewed. This means the criteria will only match if a blog entry the architecture enrichment is viewed more than one time:

    • With the first view, the score will be 50. This isn't greater than 50.
    • With the second view, the score will be 100. This is greater than 50.
  6. Click Save.

  7. Publish the entry to the environment development.

  8. Create the following entries using the content type Hero:

    TitleMessagePersonalization criteria
    Data HeroYou like data!Enrichment: Data
    Futuristic HeroYou like emerging technology!Enrichment: Futuristic
    Default HeroView some blog posts so we can learn about your interests.

    About this step

    The Default Hero entry doesn't have any personalization criteria assigned. This means that this entry will always match. This is why it's called the default hero.

  9. Publish the entries to the environment development.

The home page will display a hero banner that matches the topic the visitor is most interested in. The connection between the home entry and the hero entries must be created.

entry-home-page-model
  1. In Contentstack, open the entry Home.

  2. For the field Personalized hero, choose all 4 of the Hero entries you created earlier.

  3. Set the order for the selected entries to the following:

    • Architecture Hero
    • Data Hero
    • Futuristic Hero
    • Default Hero

    About this step

    When Uniform evaluates the personalization criteria assigned to each hero, the first criteria that matches determines which hero will be used.

    Since the default hero has no criteria assigned, it will always match (it's the default hero, after all!). So it's important that the default hero be the last entry in the list.

    The order of the other heroes isn't important since of the them has a unique criteria. However, if there were overlapping criteria (for example, one hero matches on an interest in architecture and data, then that hero would need to be above the hero that matches only on architecture and the hero that matches only on data).

  4. Click Save.

  5. Publish the entry to the environment development.

Uniform is designed to require minimal Uniform-specific product knowledge so developers can incorporate it into their apps as easily as possible. This section guides you through the process of adding personalization to the Next.js app.

tip

If you learn better by studying a fully implemented, working example, see the finished code section.

On your local machine, clone the repository that was created when you installed the NextJs Starter in your Contentstack organization.

tip

The URL for this repository is the Repository URL that was displayed after you added the NextJs Starter to your stack.

The .env file sets environment variables that the Next.js app uses to connect to external system (such as Contentstack and Uniform).

  1. Create a file .env in the root of the repository:

    CONTENTSTACK_API_KEY= CONTENTSTACK_DELIVERY_TOKEN= CONTENTSTACK_ENVIRONMENT=development
  2. In Contentstack, open your stack.

  3. Navigate to Settings > Tokens > Delivery Tokens.

  4. Click the Edit link for the delivery token.

  5. Copy the values to the .env file:

    Contentstack valueEnvironment variable
    Stack API KeyCONTENTSTACK_API_KEY
    Delivery TokenCONTENTSTACK_DELIVERY_TOKEN

    About this step

    These environment variables are needed in order for the Next.js app to be able to read content from the appropriate Contentstack environment.

  6. Add the following values to your .env file. You collected these values when you created the Uniform API key:

    Uniform valueEnvironment variable
    API KeyUNIFORM_API_KEY
    Project IDUNIFORM_PROJECT_ID

    About this step

    These environment variables are needed in order for the npm build script to communicate with Uniform.

  7. Save the file.

The Next.js application needs to have access to the personalization settings from Uniform. Uniform can generate a manifest that contains this information so you can use the settings in your application.

tip

Often, when using Uniform in an application that uses static site generation, you should generate the manifest before building the application. This ensures the latest manifest is available to the application.

  1. In Uniform, navigate to Personalization > Enrichments.

  2. Click Publish.

    About this step

    When settings are configured under personalization, they're not available to be used in an application until they're published.

  3. Open a terminal in the root of the repository.

  4. Enter the following commands:

    npm install -D @uniformdev/cli npm install @uniformdev/context

    About this step

    This adds a reference to the package with the Uniform CLI, which is a tool that enables you to interact with Uniform from a command-line interface. It also adds a reference to the package that includes a Uniform CLI extension for interacting with Uniform Context from a CLI.

  5. Add the following to package.json:

    { "name": "contentstack-nextjs-starter-app", "description": "A starter app for Contentstack and Nextjs", "version": "1.2.0", "private": true, "author": "Contentstack", "scripts": { "dev": "npm run download:manifest && next dev", "build": "npm run download:manifest && next build", "download:manifest": "mkdir lib && uniform context manifest download --output ./contextManifest.json", "start": "next start", "lint": "eslint pages/**/*.js templates/**/*.js" }, ...

    About this step

    This adds a new script that downloads the manifest from Uniform, saves it to a file named contextManifest.json, and incorporates it into the scripts that start and build the application.

  6. Add the following to your .gitignore file:

    # uniform contextManifest.json

    About this step

    This ensures the manifest file doesn't get added to your source code repository.

Adding Uniform Context to your application involves two primary tasks. One is making the manifest available to the application. This is required because it establishes the parameters for tracking and personalization. Two is creating a "context" object that's maintains state for Uniform Context within the app.

  1. Open a terminal in the root of the repository.

  2. Enter the following command:

    npm install @uniformdev/context-react

    About this step

    This adds a reference to the package that enables you to add Uniform Context into applications build using React.

  3. Open the file ./pages/_app.js in your text editor.

  4. Add the following code:

    /* eslint-disable react/prop-types */ /* eslint-disable react/react-in-jsx-scope */ import Router from "next/router"; import NProgress from "nprogress"; import "nprogress/nprogress.css"; import "../styles/third-party.css"; import "../styles/style.css"; import { UniformContext } from "@uniformdev/context-react"; import { Context } from "@uniformdev/context"; import manifest from "../contextManifest.json"; Router.events.on("routeChangeStart", () => NProgress.start()); Router.events.on("routeChangeComplete", () => NProgress.done()); Router.events.on("routeChangeError", () => NProgress.done()); function MyApp({ Component, pageProps }) { return <Component {...pageProps} />; } export default MyApp;
  5. Add the following code:

    /* eslint-disable react/prop-types */ /* eslint-disable react/react-in-jsx-scope */ import Router from "next/router"; import NProgress from "nprogress"; import "nprogress/nprogress.css"; import "../styles/third-party.css"; import "../styles/style.css"; import { UniformContext } from "@uniformdev/context-react"; import { Context } from "@uniformdev/context"; import manifest from "../contextManifest.json"; Router.events.on("routeChangeStart", () => NProgress.start()); Router.events.on("routeChangeComplete", () => NProgress.done()); Router.events.on("routeChangeError", () => NProgress.done()); const context = new Context({ manifest, defaultConsent: true, }); function MyApp({ Component, pageProps }) { return <Component {...pageProps} />; } export default MyApp;
  6. Add the following code:

    /* eslint-disable react/prop-types */ /* eslint-disable react/react-in-jsx-scope */ import Router from "next/router"; import NProgress from "nprogress"; import "nprogress/nprogress.css"; import "../styles/third-party.css"; import "../styles/style.css"; import { UniformContext } from "@uniformdev/context-react"; import { Context } from "@uniformdev/context"; import manifest from "../contextManifest.json"; Router.events.on("routeChangeStart", () => NProgress.start()); Router.events.on("routeChangeComplete", () => NProgress.done()); Router.events.on("routeChangeError", () => NProgress.done()); const context = new Context({ manifest, defaultConsent: true, }); function MyApp({ Component, pageProps }) { return ( <UniformContext context={context}> <Component {...pageProps} /> </UniformContext> ); } export default MyApp;
  7. Add the following code:

    /* eslint-disable react/prop-types */ /* eslint-disable react/react-in-jsx-scope */ import Router from "next/router"; import NProgress from "nprogress"; import "nprogress/nprogress.css"; import "../styles/third-party.css"; import "../styles/style.css"; import { UniformContext } from "@uniformdev/context-react"; import { Context, enableContextDevTools } from "@uniformdev/context"; import manifest from "../contextManifest.json"; Router.events.on("routeChangeStart", () => NProgress.start()); Router.events.on("routeChangeComplete", () => NProgress.done()); Router.events.on("routeChangeError", () => NProgress.done()); const context = new Context({ manifest, defaultConsent: true, plugins: [ enableContextDevTools(), ], }); function MyApp({ Component, pageProps }) { return ( <UniformContext context={context}> <Component {...pageProps} /> </UniformContext> ); } export default MyApp;

    About this step

    This enables the Uniform browser extension. Production sites usually don't have this enabled, but it can be helpful during development and testing.

  1. In the terminal, enter the following command:

    npm run dev
  2. Open a browser window to http://localhost:3000 to view the NextJs Starter application.

    localhost-initial
  3. If you have the Chrome extension installed, you should see that the Uniform logo is now in full color (previously it was black and white). This means that Uniform Context was detected on the site.

    localhost-initial-with-extension

When you added the field Topic to the Blog Entry content type, you made it possible for content authors to assign a topic to a blog post entry. You must update the Next.js application to track the visitor using this field value.

  1. Open the file ./pages/blog/[...post].jsx in your text editor.

  2. Add the following import statement to the file:

    import { Track } from "@uniformdev/context-react";

    About this step

    This React component handles tracking, which is the process of capturing visitor activity so it can be used later (in this case, to power personalization).

  3. Add the following code:

    export default function BlogPost(props) { const { header, banner, footer, result } = props; const behavior = result?.topic?.name; return ( <Layout header={header} footer={footer} page={banner} blogpost={result}> {banner.page_components && ( <RenderComponents pageComponents={banner.page_components} ...

    About this step

    When you see the value of the field Topic in Contentstack, it's well formatted. Behind that formatting, however, is a JSON data structure that describes the behavior the Uniform tracker should take when the visitor views the content. In the case of the topic, that behavior is "add a certain number of points to a specific enrichment".

    This behavior will be passed to the Uniform tracker.

  4. Add the following code:

    export default function BlogPost(props) { const { header, banner, footer, result } = props; const behavior = result?.topic?.name; return ( <Layout header={header} footer={footer} page={banner} blogpost={result}> {behavior && <Track behavior={behavior} />} {banner.page_components && ( <RenderComponents pageComponents={banner.page_components}

    About this step

    If a topic is set on the blog post entry, the Track component is displayed. The behavior is passed to the component as a prop. The component tracks visitor activity based on the instructions in the behavior object.

  5. Open a browser window to http://localhost:3000/blog/the-future-of-business-with-aI to view a blog entry page with a topic assigned.

    localhost-blog-entry-ai
  6. Open the browser extension and navigate to Dimensions.

    localhost-blog-entry-ai-extension-initial

    About this step

    This shows that the tracker collected data, but it's not very intuitive. It says that the dimension "1_1" has a score of "50." The "1_1" is a combination of the public IDs for the enrichment Topic and the enrichment value Futuristic.

  7. In the extension, click Settings.

    localhost-blog-entry-ai-extension-settings
  8. Enter the Uniform API key and project ID values that you added to the file .env and click Save.

    localhost-blog-entry-ai-extension-settings-set

    About this step

    These settings give the browser extension the ability to read metadata directly from Uniform. This makes it possible to translate "1_1" into something meaningful.

  9. In the extension, click Dimensions. Now you will see more meaningful descriptions of what the tracker has collected.

    localhost-blog-entry-ai-extension-dimensions
  10. In your browser, navigate to http://localhost:3000/blog/the--modern-cloud-ecosystem.

  11. Open the browser extension. Now you will see that you have scores for 2 enrichments.

    localhost-blog-entry-cloud-dimensions

    About this step

    You can see that tracking is working. The next step is to personalize the visitor's experience based on this information that has been tracked.

In Contentstack you configured a number of hero entries, each of which had its own personalization criteria assigned. Uniform provides a React component that's able to determine which of those hero entries to displayed, based on the visitor activity that has been tracked.

You must add this React component to your Next.js application.

  1. Create the file ./styles/personalization.css:

    .personalized-hero { margin: 10px 0 10px 0; padding: 40px 0 10px 0; text-align: center; background: #f7f7f7; }

    About this step

    This defines some styles that are assigned to the personalized hero component you will create.

  2. Open the file ./pages/_app.js.

  3. Add the following code to the top of the file:

    import "../styles/personalization.css";
  4. Create the file ./components/personalized-hero.js:

    import React from "react"; import { Personalize } from "@uniformdev/context-react";

    About this step

    This imports the React component that handles personalization.

  5. Add the following code:

    function Hero({ message, title }) { return ( <div> <div className="personalized-hero"> <h2>{title}</h2> <p>{message}</p> </div> </div> ); }

    About this step

    This is the React component that represents the hero. The Personalize component handles determining the props to pass to this component.

  6. Add the following code:

    function reshape(variations) { if (!variations) return; let i = 0; return variations.map((variation) => { const { personalization_criteria, ...fixed } = variation; fixed.pz = variation.personalization_criteria?.name; fixed.id = i++; return fixed; }); }

    About this step

    The Personalization component receives a collection of the possible variations of content that can be used as a result of personalization. Each of these variations must have at least two properties: id and pz. This function ensures those properties are present.

  7. Add the following code:

    export default function PersonalizedHero({ variations }) { return ( <div> <Personalize variations={reshape(variations)} component={Hero} /> </div> ); }

    About this step

    This is a component that associates the Uniform Personalize component with the Hero component for this application. It also applies the reshape function to ensure the variations are in a format that's compatible with the Personalize component.

  8. Open the file ./pages/index.jsx.

  9. Add the following code to the top of the file:

    import PersonalizedHero from "../components/personalized-hero";

    About this step

    This imports the personalized hero component so you can use it in this file.

  10. Add the following code to the function Home:

    export default function Home(props) { const { header, footer, result } = props; const { personalized_hero } = result; return ( <Layout header={header} footer={footer} page={result}> {result.page_components && ( <RenderComponents pageComponents={result.page_components} contentTypeUid="page" entryUid={result.uid} locale={result.locale} /> )} </Layout> ); }

    About this step

    This creates a variable for accessing the value of the Personalized hero field from the Page content type.

  11. Add the following code:

    export default function Home(props) { const { header, footer, result } = props; const { personalized_hero } = result; return ( <Layout header={header} footer={footer} page={result}> {personalized_hero && <PersonalizedHero variations={personalized_hero} />} {result.page_components && ( <RenderComponents pageComponents={result.page_components} contentTypeUid="page" entryUid={result.uid} locale={result.locale} /> )} </Layout> ); }

    About this step

    If the Personalized hero field has a value, then use the PersonalizedHero component.

  12. Add the following code to the function getServerSideProps:

    export async function getServerSideProps(context) { try { const result = await Stack.getEntryByUrl({ contentTypeUid: "page", entryUrl: context.resolvedUrl, referenceFieldPath: [ "page_components.from_blog.featured_blogs", "personalized_hero", ], jsonRtePath: [ "page_components.from_blog.featured_blogs.body", "page_components.section_with_buckets.buckets.description", ], }); ...

    About this step

    The Personalized hero field on the Page content type is a reference field. When you retrieve data for an entry, you must specify which reference fields you want to include. This line ensures the Personalized hero reference field is included.

We provide a finished version of the NextJs Starter application that you can get running in your preferred environment.

import { StackblitzLink, CloneGithubRepoCommands } from '@site/src/components/FinishedApp'; const branch = "main" const path = "examples/docs/apps/contentstack/nextjs/finished" const title = "Contentstack App for Uniform - NextJs Starter - FINISH" const stackblitzOptions = { terminal: 'dev', file: '.env', };

  1. Open StackBlitz to create your development environment.

  2. After you open StackBlitz you will see an error in the terminal that environment variables aren't set. You must set the variables in .env to match the settings that apply to your Contentstack and Uniform environments.

  3. Changing the .env file should cause the container to restart. If it doesn't, enter the following command in the StackBlitz terminal:

    npm run dev
  4. In the activity bar the plug icon indicates that 1 port is in use. Click this icon.

    stackblitz-ports-in-use
  5. You will see links to the web app running on the 1 port. Click the link for Port 3000.

    stackblitz-ports-in-use-2
  6. A new browser tab opens. This is your Next.js application running in develop mode.

    https://uniformdev-uniform-docs-examples-######--3000.local.webcontainer.io/