Sitecore Personalize – Livin’ on the Edge

With the explosion of data being collected and consumed by IoT applications and devices along with our insatiable desire for near real-time feedback, has led to a shift in how we handle computing. Performing all the computation at data centers and/or cloud servers is not the most efficient approach as this increase in data consumption requires significantly more bandwidth which only increases latency. Edge computing, allows computation of data to remain much closer to the user rather than going through several network hops, for a cloud server to process the data and return a response. With this computation of data happening closer to the source not only is Edge more performant it is also considered more secure.

Users who have a personalized online experience and feel a connection with your brand are more likely to make a purchase and become loyal customers. However, a poor implementation that adversely impacts performance will have the opposite effect and drive potential customers away.

The probability of bounce increases 32% as page load time goes from 1 second to 3 seconds.

https://www.thinkwithgoogle.com/marketing-strategies/app-and-mobile/page-load-time-statistics/

In this post, I will demonstrate how you can improve performance and user experience by moving personalization to the Edge with Nextjs Middleware and Sitecore Personalize using the Sitecore Commerce template created by Vercel. First I’ll show you how to personalize using visitors’ Geolocation to show summer or winter clothing collections.

Then we’ll personalize using guest information collected by Sitecore Personalize to display products from our men’s or women’s store.

Vercel Edge Middleware

Vercel Edge Middleware uses the Vercel Edge Runtime, and is built on the same high-performance V8 JavaScript engine used by Chrome. It was made available in Nextjs v12.0.0 as beta and recently released in 12.2.0. Support for all other frameworks are still in beta. By exposing and extending web standard APIs: FetchEvent, Response and Request you are able to manipulate responses based on incoming requests at the edge.

When deploying a Next.js application to Vercel, the middleware will deploy as Edge Functions to its various regions around the world. This means that instead of a function sitting on a single server, it will sit on multiple servers. The server nearest to the request source will handle the request.

With edge middleware being deployed globally on Vercel’s Edge Network and executing before a request is processed or cached on a web server, you are able to move certain logic closer to the site visitor and provide faster, more seamless personalized experiences.

Edge Middleware enables you to deliver dynamic, personalized content without sacrificing end-user performance.

https://vercel.com/features/edge-functions

Personalization and localization

Personalization and localization is a critical marketing strategy for brands looking to compete in an increasingly competitive global market. It allows them to convey the right message or product to the right consumer at the right time and location, at scale. For example, take a global clothing brand if we knew our visitor’s location we could display a range of products suitable to their climate.

To demonstrate this I’ll use the NextJS Commerce template and add some seasonal product pages. Customers visiting the site located in the Northern hemisphere would see the summer collection while visitors in the Southern Hemisphere would see products from our winter collection when they click on the seasonal navigation link.

  • Step 1 – Add a Middleware function to the solution.
  • Step 2 – Add a custom match config.
  • Step 3 – Using geo info determine hemisphere rewrite url on the response.
  • Step 4 – Deploy to Vercel & test.

Step 1 – To create a Middleware function we need to add a file at the same level as your pages directory middleware.ts (or .js). This is where code that will be deployed as middleware will live.

Step 2 – Edge middleware is invoked on every route by default, we will add a custom match config to ensure our middleware function only runs on a specific route ‘/seasonal’. Note: While you could also conditional statements within your Middleware function to check specific paths the custom matcher config is the preferred method. As it will only run the middleware when the path matches the config.

Step 3 – Using the geo object from the request we can add some logic to check whether user is in the Northern or Southern Hemisphere and determine the season and modify the URL accordingly. The NextResponse class provides two static methods that will allow you to change the URL:

  • redirect() – returns a NextResponse with a redirect set, this modifies the url in the users browser.
  • rewrite() – returns a NextResponse with a rewrite set, this will modify the URL without reflecting the change in the browser.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// Trigger this middleware to run on the `/seasonal` route
export const config = {
matcher: '/seasonal',
};
/**
** Middleware Function to rewrite seasonal url based on user geo location
* @param req
* @returns NextResponse with a rewrite set
*/
export function middleware(req: NextRequest) {
// Extract country from Request Geo object. Default to US if not found and write to console
const country = (req.geo && req.geo.country) || 'US';
console.log(`Visitor from ${country}`);
// Extract Latitude from request Geo object and determine Hemisphere
const hemisphere = (req.geo && req.geo.latitude) as any > 0 ? 'Northern' : 'Southern';
console.log(`Visitor located in ${hemisphere} Hemisphere`);
// Obtain Season using Visitor Hemisphere
let season = getSeason(hemisphere);
req.nextUrl.pathname = `/seasonal/${season}`;
console.log(`Rewrite path based on Visitor season: ${req.nextUrl.pathname}`);
// Rewrite URL to personalize visitor journey based on season
return NextResponse.rewrite(req.nextUrl);
}
..
...

Step 4 – When we push our changes to our git repo Vercel will build and deploy our solution ready for testing.

As I’m currently located in the Northern hemisphere and it’s the middle of August, I expect to see the Summer products collection when I navigate to ‘/seasonal’:

If I change my location to Australia, I now see the Winter collection. That is also why you see everyone in the SUGCON ANZ 2022 wearing sweaters and jackets:

Within Vercel we can view the console logging I added to my middleware function by checking Vercel Functions Log and selecting middleware:

While this is a fairly straightforward example it does demonstrate how handling the URL rewrite or redirects at the edge, rather than the client, improves user experience, and prevents content flashing/jumping that would occur if performing this on the client-side. Pretty sweet right?

Personalization using Sitecore CDP

With Sitecore CDP & Personalize we can easily track user behavior, collecting information about our visitors, and using this info we can personalize their user experience either through simple or complex decision model rules to define personalization or even utilize an AI engine to determine intent-based personalization. Why not do this at the Edge? In this next example, l will use a simple example and return some visitor information from the guest data model and rewrite the URL to display our men’s or women’s clothing store pages based on the guest’s information. This information could have been provided by our guest either through checkout or a subscription form while we were tracking user’s behavior, for example:

To personalize at the Edge using the Sitecore Personalize guest data model we will perform the following steps:

  • Step 1 – Create a Fullstack Interactive Web Experience in Sitecore Personalize
  • Step 2 – Create a browser Reference using Sitecore CDP Stream APIs
  • Step 3 – Retrieve the Guest Data model using Sitecore CDP flow execution API
  • Step 4 – Modify Middleware function for Guest data Personalization
  • Step 5 – Create Vercel Environment variables
  • Step 6 – Deploy to Vercel & test.

Step 1 – Create Full Stack Interactive Web Experience

To retrieve data from our guest data model stored in Sitecore Personalize we can create a Full Stack Interactive Web Experience which we will call Get Guest Information. In the API tab of the default variant, we’ll construct a JSON response using the guest data model and Freemarker.

{
"title": <#if guest.title??>"${guest.title}"<#else>""</#if>,
"firstName": <#if guest.firstName??>"${guest.firstName}"<#else>""</#if>,
"lastName": <#if guest.lastName??>"${guest.lastName}"<#else>""</#if>,
"gender": <#if guest.gender??>"${guest.gender}"<#else>""</#if>,
"guestType": <#if guest.guestType??>"${guest.guestType}"<#else>""</#if>
}

You can easily test the API response in the API editor by clicking the Preview API button and selecting an existing guest account. Once you are happy with the API response you can Save and Close the variant and Start the experience.

Step 2 – Create a browser reference

The browser reference (or bid) is the value that is used to identify a guest (anonymous or known) within Sitecore CDP. Within our Edge middleware, we will check if this exists and if not create it. As we are running on the Edge we can use the server-side Sitecore CDP Stream APIs and create a browser reference using a fetch request calling the Browser API: https://{apiEndpoint}/v1.2/browser/create.json
Passing the following parameters:

  • client_key – your organizations unique client key.
  • message – the body of the message. We can leave this empty when retrieving the browser reference.
/**
* This function is used to Get the Browser Ref from Sitecore Personalize
* @returns browserId
*/
async function getBrowserRef()
{
let browserId = "";
var result = null
try
{
let request = `https://${cdpEndpoint}xx/v1.2/browser/create.json?client_key=${clientKey}&message={}`
const response = await fetch(request, {method: 'GET'});
if (response.ok)
{
result = await response.json();
if(isGuid(result.ref)){
browserId = result.ref
}
}
}
catch(err:any)
{
console.log(`getBrowserRef Error retrieving Browser from Sitecore Personalize: ${err}`)
}
finally {
return browserId;
}
}
view raw getBrowserRef hosted with ❤ by GitHub

Step 3 – Run the Experience on the Edge

Next we need to execute our experience and return the API response. We can use the Sitecore CDP Stream APIs and make a fetch request to the Flow Execution API. The flow execution API allows us to run fullstack web experiences using its friendly name. Using a fetch we can call the API: https://{apiEndpoint}/v2/callFlows with the following data model properties :

  • client_key – your organization’s unique client key.
  • channel – your specific channel, i.e ‘WEB’.
  • language – your language captured on each page the guest visited, i.e ‘EN’.
  • currency – your the type of currency, i.e ‘USD’.
  • pos – configured point of sale, i.e ‘developerswag’.
  • browser_id – the ID of a browser generated by Sitecore CDP.
  • friendlyId – the id of full stack experience. You can retrieve this from the details tab on the experience. For this example it is ‘get_guest_information’.
/**
* Gets Guest Information using Sitecore Personalize Flow Execution
* @param browserId
* @returns json object
*/
async function getGuestInformation(browserId:any)
{
let webExperienceId = 'get_guest_information'
var result = null
try
{
let flowExecutionModel = {channel: channel,
language: language,
currencyCode: currency,
pointOfSale: pos,
clientKey: clientKey,
friendlyId: webExperienceId,
browserId: browserId}
let request = `https://${cdpEndpoint}/v2/callFlows`
const response = await fetch(request, { method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(flowExecutionModel)
})
if(response.ok)
{
result = await response.json();
}
}
catch(err:any)
{
console.log(`getGuestInformation Error retrieving Guest Information from ${webExperienceId}: ${err}`)
}
finally {
return result;
}
}

Note: This is a simple fetch method – not production ready. It needs to incorporate better error handling and the ability to abort the request for handling potential timeouts imposed by the Middleware limitations.

Once we have our guest data we can check the value of the gender field and rewrite the url to either ‘/store/womens‘ or ‘/store/mens’ to display clothing products from the store they might be most interested in purchasing from.

/**
** The handleStoreRequest Function is used to rewrite store url
** based on user information stored in Sitecore Personalize
* @param req
* @returns NextResponse with a rewrite set
*/
async function handleStoreRequest(req: NextRequest) {
// Check if we have a browserId in the request cookie collection
let browserId = getCookie(req.headers.get('cookie'), browserIdCookieName)
let hasBrowserIdCookie = !!browserId
if(!hasBrowserIdCookie){
browserId = await getBrowserRef()
}
// Attempts to retrieve Guest information from Sitecore Personalize
let guest = await getGuestInformation(browserId);
if(!!guest)
{
// Rewrite store url based on guest gender stored in CDP
let shopRoute = shopRoutes.find(route => route.key==guest.gender)?.value
|| req.nextUrl.pathname
req.nextUrl.pathname = shopRoute
}
const response = NextResponse.rewrite(req.nextUrl)
console.log(`HandleStoreRequest - Rewrite path : ${req.nextUrl.pathname}`);
// Add Cookie to response if it did not already exist
if(!hasBrowserIdCookie){
response.cookies.set(browserIdCookieName, browserId, cookieOptions);
}
return response
}

Step 4 – Refactor the Edge Middleware

As we already have a Middleware function running against a specific path we will add another path for ‘/store’ to our match config. So our middleware now only runs when the request path matches either ‘/seasonal’ or ‘/store’.

// Trigger this middleware to run on the `/seasonal` and `/store` route
export const config = {
matcher: ['/seasonal', '/store'],
};

We’ll refactor the existing seasonal personalization logic into a separate function and call it from the middleware function when the requests path is equal to ‘/seasonal’. We will call our new function to rewrite the url when the request path matches ‘/store’.

/**
** Middleware Function to rewrite shops url based on user information stored in Sitecore Personalize
* @param req
* @returns NextResponse with a rewrite set
*/
export function middleware(req: NextRequest){
// Check if request is seasonal content and handle request to personalize experience
if(req.nextUrl.pathname.startsWith("/seasonal"))
{
return handleSeasonalRequest(req)
}
// Check if request is store content and handle request to personalize experience
if(req.nextUrl.pathname.startsWith("/store"))
{
return handleStoreRequest(req)
}
}

Step 5 – Create Environment Variables

Our new Edge Middleware contains some new variables for connecting and sending requests to the Sitecore CDP Stream APIs we add these to our .env.local and configure them in Vercel.

  • SITECORE_PERSONALIZE_ENDPOINT
  • SITECORE_PERSONALIZE_CLIENTKEY
  • SITECORE_PERSONALIZE_POS
  • SITECORE_PERSONALIZE_CHANNEL
  • SITECORE_PERSONALIZE_LANGUAGE
  • SITECORE_PERSONALIZE_CURRENCY

Step 6 – Deploy to Vercel

I just need to merge my changes to the branch and push as Vercel provides automatic deployments on every branch push. Wait a few minutes for Vercel to finish building and deploying. And we are ready to test.

Now when I navigate to ‘/store’ and Sitecore Personalise returns ‘female’ our Edge function rewrites the response URL and am shown the women’s clothing store page:

If Sitecore Personalise returns ‘male’ our Edge function rewrites the response URL and am shown the men’s clothing store page:

If my guest information is not returned or I’ve not specified gender the user can choose from the different stores available on ‘/store/index’ page.

Nextjs Edge Middleware Limitations

As the Nextjs middleware is using the Edge runtime you can execute V8 code and a full list of available APIs is provided here. It does have some limitations you should be aware of:

  • Native Node.js APIs are not supported.
  • Node Modules can be used, as long as they implement ES Modules and do not use any native Node.js APIs.
  • You can use ES Modules and split your code into reusable files that will then be bundled together when the application is built.
  • Calling ‘require’ directly is not allowed.
  • eval – Evaluates JavaScript code represented as a string.
  • new Function(evalString) – Creates a new function with the code provided as an argument.
  • The maximum duration for Edge Middleware execution is 5 seconds.
  • An Edge function can only use up to 128MiB of memory. If it exceeds this limit, the execution will be aborted and we will return a 502 error.
  • The maximum size for Edge Middleware is 1 MiB after compression, inc your JS code, imported libraries, and any files bundled in the function.
  • The maximum number of requests from fetch API is 950.
  • Date.now() only advances after I/O operations.

Every Vercel account has 1 million monthly Middleware invocations included for free. Pro and Enterprise teams can pay for additional usage.

Useful Info

Rock on over to the Edge!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s