Building with CDP/Personalize on Next.js +XM/XP

Index
Summary
In this post, I will share my general thoughts after having spent some time with CDP and Personalize. I will connect some dots to supplement the official documentation and go a little deeper to discuss some key questions and learnings along the way.
While this post discusses concepts which are applicable to any stack, code examples will focus on a Next.js app using the Engage SDK package with server-set cookies on XM/XP implementations.
I recommend checking out the official docs that cover how the integration works.
Otherwise, let's dive in.

Concepts
The following concepts and keywords are covered repeatedly throughout this post:
- Experiments / experiences - I refer to these broadly as "flows"
- Web vs. full stack flows: Web vs. Interactive/Triggered flows
- Tracking: sending events (
VIEW,IDENTITY, etc.) to or getting data from CDP - Personalizing / personalization
Personalize - Web Experiments/Experiences
The quickest way to understand what web flows are and what they are useful for is to look at the OOB templates that ship with Personalize:

Notice how they are all relatively independent of the main parts of the DOM. Popups, sidebars, widgets, alert bars. They are "islands." These types of components are good candidates for web flows because they:
- Are relatively simple
- Are unlikely to interfere with code defined in your head application
- Can be built and shipped quickly
- Aren't negatively affected by flicker because they are all somewhat expected to appear after the initial page paint
Theoretically, you can build anything you want with web flows because they allow you to inject HTML, CSS, and JavaScript. However, if you want to personalize components that are defined in your head application, you are likely going to want to use full stack (Interactive) flows rather than just web flows.
Be careful when mixing web flows with React apps
React manages the DOM through its virtual DOM system. If you modify the DOM outside of React (injecting elements or change content with plain JavaScript), React may overwrite or undo those changes on the next render. This can lead to unpredictable UI behavior, lost personalization, or even broken components.
Full stack flows, on the other hand, allow you to integrate personalization logic within your React components and server-side rendering, ensuring that personalized content is part of the React render lifecycle and not at risk of being lost or causing conflicts.
Pages Router vs App Router
Next.js supports two routing paradigms: the Pages Router (the traditional pages/ directory) and the App Router (the newer app/ directory with React Server Components).
Sitecore's new Content SDK for Next.js supports App Router, but Content SDK is only compatible with SitecoreAI (previously known as XM Cloud).
For now, XM/XP implementations will continue to use the Pages Router (by virtue of the fact that the JSS SDK only supports Pages Router). There is an interesting discussion to keep an eye on. Will Sitecore ever add App Router support to the JSS SDK? How long will Vercel support the Pages Router? Will the Pages Router be deprecated in favor of the App Router?
Server-Side or Bust
The Sitecore docs on how to integrate a Next.js app with Engage SDK (server-set cookies) seem to give client-side integration details more weight than I would have expected. Certainly there is a place for integrating on the client-side, particularly if you want to run web flows, but generally when we are working with head apps such as Next.js, a full stack approach will be more appropriate for enterprise use cases.
The benefits of server-side tracking are clear:
- Increases data security - you can handle sensitive data on the server without having to expose it on the client side.
- Increases data flexibility - on the server, you can extend the data, integrate it with other external systems, or otherwise customize it before forwarding it to Sitecore CDP.
- Prevents ad blockers and web browsers from blocking the tracking code or otherwise manipulating the data.
- Improves website performance by reducing the number of network requests to external systems. This improves website speed and ensures that data is captured even on poor network connections.
If you want a seamless and bulletproof implementation of CDP/Personalize, client-side will take a back seat to server-side. Client-side anything has numerous problems/flaws, one of them being that when you send behavioral and transactional data from the client-side of your app (via OOB functions), you are sending it directly to Sitecore API endpoints rather than through your server first.
NEWS FLASH
Endpoints such as api-engage-us.sitecorecloud.io are well known in anti-tracker/privacy/ad block communities. If you want to be certain that your events are actually being sent to CDP and not being blocked or fiddled with along the way, you can't be calling these OOB endpoints client-side. Server-side tracking means that you send the data from the client to your server first. Then, you forward the data from your server to Sitecore CDP (docs). Likewise, server-side personalization means that you call your own API endpoint for personalized content (example).
Server-Side in Practice
In a Next.js app, all requests that match your path matcher config will be processed by your middleware.
This is the place to initialize the Engage SDK, get/set cookies, and potentially fire events (in this case, VIEW).
Geolocation
Beware the legacy Boxever integration
The legacy Boxever integration does not collect session-based geolocation information automatically. It also has a host of other shortcomings. Migrate to Engage SDK or Cloud SDK (for SitecoreAI) ASAP.
Engage/Cloud SDK automatically collect session-based geo data such as city, region, country, and continent based on the user's IP address. In addition to being automatically populated in CDP data, some of the geo data is also accessible in Personalize via OOB conditions.
Geo information is available because user requests to Sitecore's API ostensibly route through Cloudflare. Cloudflare modifies requests to include the geo in request headers such as cf-ipcountry. We can inspect and work with those requests/headers in Personalize.
Sitecore Personalize has some OOB ways of leveraging geo data such as continent, country, and region (only US states at this time). Otherwise, if you need to work with geo that is at the city-level or in "regions" that are outside the USA (provinces, etc.), additional setup is required in Personalize (custom code/conditions). However, you need to create custom JS modules to get it from session.
Example of a custom JS module that can be used to get geo data from session:


Example of a custom condition that can be used to check the user's city (keep in mind that you also need to factor in the state):


Geo-Based CDP Segments
You can create segments in CDP based on session geo data:

Notice how region is not implemented OOB -- you will need to write custom SQL for that. Luckily it's easy to start with an OOB query and update the field it pulls from:
The one OOB geolocation condition is for US states only:

Flicker
Any time you are running personalization on the client side, you are going to experience flicker. The page was painted, and then a call to personalize injects/updates the page, hence the flicker.
In this example component in the XMC official JS starter repo, Sitecore gets and sets an isLoading flag to toggle a placeholder element while the component personalization fetch is in flight:
This is a rudimentary approach, but it's a step in the right direction.
Solutions & Workarounds
- Suspense states
- Hide personalized elements areas until
- Decrease latency as much as possible with pre-fetch, loading scripts as early as possible, etc
- Use server-side rendering
Additional Considerations
- Think about scenarios in which CDP/P should be disabled. I did most of the hard work for you here.
- Check out the official XMC js starter kit for code snippets mentioning
cdpandpersonalize.
Keep it personal,
-MG





