cs-icon.svg

Setup Next.js Website with Personalize - Launch

This guide will help you set up your Next.js website with Personalize, hosted on Launch.

Prerequisites

  • Next.js website on the latest version (14 and above) with app router
  • Website content sourced from a Stack
  • Website deployed on Launch
  • Personalize project created and connected to the Stack

Steps for Execution

  1. Get Personalized project UID
  2. Proxy requests with Launch
  3. Fetch variant content from the origin
  4. Set up attributes and trigger events

Get Personalize Project UID

To retrieve the project UID, log in to your Contentstack account and perform the steps given below:

  1. Navigate to Personalize and select your preferred project.

  2. Click the Settings icon in the left navigation panel.

  3. In the General tab, under Project Details, you will find the 24-character project UID.

    Fetch_Personalize_Project_UID.png
  4. Click the Copy icon to copy the project UID to your clipboard. We will need this UID while setting up the Personalize Edge SDK in the next step.

Proxy Requests with Launch Edge Proxy

Launch Edge Proxy allows you to execute your code in proximity to your user’s location before a request is processed. Using this feature, we can make a call to the Personalize Edge API and fetch the User Manifest for each visitor. The User Manifest contains the selected Variant for each Experience. We can then pass these variants as the URL query parameters.

We would use the Launch Edge Proxy, since the Next.js Middleware executes on the server side and not in the Edge when hosted on Launch.

Install the Personalize SDK

To install the Personalize Edge SDK in your Next.js project:

$ npm install @contentstack/personalize-edge-sdk

Additional Resource: The API Reference for the SDK contains a lot of information on how to use the SDK.

Create the Edge Function Handler

The [proxy].edge.js is to be created in the /functions/ folder inside your website source code. To create the Edge Function Handler:

// [proxy].edge.js
export default async function handler(request, context) {
  return fetch(request);
}

Initialize the SDK

Now modify the [proxy].edge.js file with the following code to initialize the Personalize Edge SDK:

import Personalize from '@contentstack/personalize-edge-sdk';
export default async function handler(request, context) {
  // set a custom edge API URL
  if (context.env.NEXT_PUBLIC_CONTENTSTACK_PERSONALIZE_EDGE_API_URL) {
    Personalize.setEdgeApiUrl(context.env.NEXT_PUBLIC_CONTENTSTACK_PERSONALIZE_EDGE_API_URL);
  }
  // Initialize the SDK and pass the request as well
  await Personalize.init(context.env.NEXT_PUBLIC_CONTENTSTACK_PERSONALIZE_PROJECT_UID, {
    request,
  });
  return fetch(request);
}

Pass the variant parameter in the URL

Now add the following code to the [proxy].edge.js file to pass the variant parameter in the URL:

import Personalize from '@contentstack/personalize-edge-sdk';
export default async function handler(request, context) {
  const parsedUrl = new URL(request.url);
  // reset the SDK to remove any existing context
  Personalize.reset();
  if (context.env.NEXT_PUBLIC_CONTENTSTACK_PERSONALIZE_EDGE_API_URL) {
    Personalize.setEdgeApiUrl(context.env.NEXT_PUBLIC_CONTENTSTACK_PERSONALIZE_EDGE_API_URL);
  }
  await Personalize.init(context.env.NEXT_PUBLIC_CONTENTSTACK_PERSONALIZE_PROJECT_UID, {
    request,
  });
  // get the variant parameter from the SDK
  const variantParam = Personalize.getVariantParam();
  // set the variant parameter as a query param in the URL
  parsedUrl.searchParams.set(Personalize.VARIANT_QUERY_PARAM, variantParam);
  // rewrite the request with the modified URL
  const modifiedRequest = new Request(parsedUrl.toString(), request);
  const response = await fetch(modifiedRequest);
  return response;
}

Add Response Context

Now add the following code to the [proxy].edge.js file to add the response context:

import Personalize from '@contentstack/personalize-edge-sdk';
export default async function handler(request, context) {
  const parsedUrl = new URL(request.url);
  Personalize.reset();
  if (context.env.NEXT_PUBLIC_CONTENTSTACK_PERSONALIZE_EDGE_API_URL) {
    Personalize.setEdgeApiUrl(context.env.NEXT_PUBLIC_CONTENTSTACK_PERSONALIZE_EDGE_API_URL);
  }
  await Personalize.init(context.env.NEXT_PUBLIC_CONTENTSTACK_PERSONALIZE_PROJECT_UID, {
    request,
  });
  const variantParam = Personalize.getVariantParam();
  parsedUrl.searchParams.set(Personalize.VARIANT_QUERY_PARAM, variantParam);
  const modifiedRequest = new Request(parsedUrl.toString(), request);
  const response = await fetch(modifiedRequest);
  const modifiedResponse = new Response(response.body, response);
  // add cookies to the response
  await Personalize.addStateToResponse(modifiedResponse);
  // ensure that the response is not cached on the browser
  modifiedResponse.headers.set('cache-control', 'no-store');
  return modifiedResponse;
}

Exclude Asset Calls

We do not need to invoke the proxy logic for Next.js asset calls. By adding a quick check at the start of our edge function, we’re able to exclude them and only include page requests for processing in the edge function, achieving better performance.

import Personalize from '@contentstack/personalize-edge-sdk';
export default async function handler(request, context) {
  const parsedUrl = new URL(request.url);
  const pathname = parsedUrl.pathname;
  // exclude Next.js asset calls so that only page requests are processed
  if (['_next', 'favicon.ico'].some((path) => pathname.includes(path))) {
    return fetch(request);
  }
  // reset the SDK to remove any existing context
  Personalize.reset();
  // set a custom edge API URL
  if (context.env.NEXT_PUBLIC_CONTENTSTACK_PERSONALIZE_EDGE_API_URL) {
    Personalize.setEdgeApiUrl(context.env.NEXT_PUBLIC_CONTENTSTACK_PERSONALIZE_EDGE_API_URL);
  }
  // Initialize the SDK and pass the request as well
  await Personalize.init(context.env.NEXT_PUBLIC_CONTENTSTACK_PERSONALIZE_PROJECT_UID, {
    request,
  });
  const variantParam = Personalize.getVariantParam();
  parsedUrl.searchParams.set(Personalize.VARIANT_QUERY_PARAM, variantParam);
  const modifiedRequest = new Request(parsedUrl.toString(), request);
  const response = await fetch(modifiedRequest);
  const modifiedResponse = new Response(response.body, response);
  // add cookies to the response
  await Personalize.addStateToResponse(modifiedResponse);
  // ensure that the response is not cached on the browser
  modifiedResponse.headers.set('cache-control', 'no-store');
  return modifiedResponse;
}

Fetch Variant Content from the Origin

The final step is to fetch the variant content using the variant parameter passed in the URL. This step needs to be performed for all the pages that need Personalize enabled.

Move to Server Side Rendering (SSR) + Cache Headers

Launch uses cache headers to cache content with SSR, hence we need to change our Next.js rendering mode to SSR. This is because we want to execute backend logic to render uncached requests, so that the right variants can be rendered for each unique request.

Please follow the Launch documentation steps to move to SSR and cache headers here. Essentially, this step involves force rendering the page on the server side and setting the appropriate cache headers.

The cache headers recommended in the above documentation can be tweaked for our personalized setup. We need to replace stale-while-revalidate with must-revalidate in the example code, to ensure that the personalized page is available immediately as the user navigates the website.

Fetch Personalized Variants from the CMS

Here, we modify the Homepage (/app/page.ts) to fetch Personalized variants from the CMS. Similarly, add the code to the other pages of your website source code.

import Personalize from '@contentstack/personalize-edge-sdk';
import contentstack from '@contentstack/delivery-sdk';
const Page = async ({
  searchParams,
}: {
  searchParams: Record<string, string>;
}) => {
  // extract the variant parameter from the URL
  let variantParam = decodeURIComponent(
    searchParams[Personalize.VARIANT_QUERY_PARAM]
  );
  // initialize the CMS Delivery SDK
  const stack = contentstack.stack({
    apiKey: process.env.NEXT_PUBLIC_CONTENTSTACK_API_KEY,
    deliveryToken: process.env.NEXT_PUBLIC_CONTENTSTACK_DELIVERY_TOKEN,
    environment: process.env.NEXT_PUBLIC_CONTENTSTACK_ENVIRONMENT,
    host: process.env.CONTENTSTACK_DELIVERY_API_HOST,
  });
  // the call to fetch a specific entry
  const entryCall = stack
    .contentType(process.env.NEXT_PUBLIC_CONTENTSTACK_HOMEPAGE_CONTENTTYPE_UID)
    .entry(process.env.NEXT_PUBLIC_CONTENTSTACK_HOMEPAGE_ENTRY_UID);
  let entry;
  if (variantParam) {
    // convert the variant parameter to variant aliases
    const variantAlias = Personalize.variantParamToVariantAliases(variantParam).join(',');
    // pass the variant aliases when fetching the entry
    entry = await entryCall.variants(variantAlias).fetch();
  } else {
    // fetch the entry without the variant aliases
    entry = await entryCall.fetch();
  }
  // render the page with the entry
};

Set Up Attributes and Trigger Events

Setting attributes and triggering events can be done in the following ways:

  1. Integrating with a CDP

  2. Using Google Tag Manager

  3. Using the SDK in the code (JavaScript Personalize Edge SDK)

Set Attributes

The following snippet shows how to set an attribute submitted via a form for a user’s age:

'use client';
import { useContext } from 'react';
import { PersonalizeContext } from '../context/PersonalizeContext';
const Page = () => {
  const Personalize = useContext(PersonalizeContext);
  const onFormSubmit = async (form) => {
    await Personalize.set({ age: form.age });
  }
  // render the component
}

Trigger Events

Impressions

To let Personalize know that a particular experience is being shown to the user, you can trigger an impression event. The following snippet shows how to trigger impressions:

'use client';
import { useContext, useEffect } from 'react';
import { PersonalizeContext } from '../context/PersonalizeContext';
const Page = () => {
  const Personalize = useContext(PersonalizeContext);
  useEffect(async () => {
    await Personalize.triggerImpression(MY_EXPERIENCE_SHORT_UID);
  }, []);
  // render the component
}

Conversions

Any action performed by the user can be a conversion event if it leads to a positive outcome. To let Personalize know that an action has been performed, trigger the event as follows:

'use client';
import { useContext } from 'react';
import { PersonalizeContext } from '../context/PersonalizeContext';
const Page = () => {
  const Personalize = useContext(PersonalizeContext);
  const onClickLearnMore = async (e) => {
    e.preventDefault();
    await Personalize.triggerEvent('learnMoreClicked'); // here 'learnMoreClicked' is the eventKey
  }
  // render the component
}

Reference Project

You can refer to the following project for a reference implementation: Next.js example GitHub repository and find it hosted here: https://personalize-demo.contentstackapps.com

Was this article helpful?
^