> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://contentful.com/developers/docs/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://contentful.com/_mcp/server. # Troubleshooting > Solutions to common Contentful Personalization setup issues. This guide covers the most common issues developers encounter when setting up and running Contentful Personalization, along with their causes and solutions. Use this guide to check your setup and troubleshoot those issues. ## Personalization silently does nothing ### Content types were not extended * **Symptoms**: Experiences are configured in Contentful, but the front-end always shows the baseline. The `nt_experiences` field does not appear on your entries. * **Cause**: The Contentful Personalization app was installed, but the content types were never [configured for personalization](https://www.contentful.com/help/personalization/set-up-contentful-personalization/). Without the `nt_experiences` field, there is nothing for the SDK to personalize. * **Solution**: Log in to the Contentful web app, open the [Contentful Personalization app configuration](https://www.contentful.com/help/personalization/set-up-contentful-personalization/), and select the content types that should be personalizable. After saving, verify that your entries now have the `nt_experiences` field. ### Include depth is too low * **Symptoms**: Entries have `nt_experiences` links in Contentful, but `ExperienceMapper.isExperienceEntry()` filters them all out. The component always renders the baseline. * **Cause**: Your Contentful API queries use an `include` depth that is too low. Experience entries and their variant references need at least two levels of resolution to be usable. * **Solution**: Increase the `include` parameter in your `getEntries` or `getEntry` calls to at least `3`. Complex page structures with nested sections often use `include: 10`. ```diff const entries = await client.getEntries({ content_type: 'page', 'fields.slug': slug, - include: 1, + include: 3, }); ``` ### Experience configurations are nested * **Symptoms**: Entries have `nt_experiences` links in Contentful and the variant references are resolved, but some components always render the baseline. One of the resolved variants is linked to another entry that has the `nt_experiences` field. * **Cause**: You configured nested experiences in Contentful that target different audiences. This causes unexpected behavior. * **Solution**: Combine your audience rules and configure the experience on a top level component. Consider the following example: * You personalized a **Hero banner** targeted towards the **Social Media Visitors** audience, which is the parent component to a personalized Action button for the **Returning Visitors** audience. If both audiences are assigned to a visitor, the Experience API might only consider one of the experiences. In this case, we recommend you create two new audiences **Social Media First-time Visitors** and **Social Media Returning Visitors**, and you configure the experiences on a content type that holds the **Hero banner**, for example, a Section component. ## Provider and scope issues ### Some pages personalize, others do not * **Symptoms**: Personalization works on certain pages but not others, even though the entries have experiences configured. * **Cause**: The `NinetailedProvider` is mounted too low in the component tree. Components outside the provider's scope cannot access personalization. * **Solution**: Move the provider higher so that all personalizable components are within its scope. If you're using Next.js, you can typically move it to `pages/_app.tsx` (Pages Router). If you're using React, move it to the root layout wrapper (App Router). ### Hydration mismatch warnings * **Symptoms**: Browser console shows React hydration warnings. Variants may flicker or render incorrectly on first load. * **Cause**: The server-rendered HTML and the client-side initial render produce different output. This often happens when the server renders a personalized variant but the client initializes with baseline data before the SDK loads. * **Solution**: Ensure the data contracts between server and client are consistent. In hybrid SSR setups, pass the experience selections to the client deterministically so the initial render matches. ## Analytics and tracking issues ### Double-counted page views * **Symptoms**: Analytics show approximately twice the expected number of page views. * **Cause**: This happens in two common scenarios: 1. **Pages Router with manual tracking**: The Next.js Pages Router integration already tracks page changes automatically. Adding manual `page()` calls creates duplicates. 2. **Hybrid SSR without preflight**: Both the server and the client SDK send page events. Without preflight mode, both events are persisted. * **Solution**: * For scenario 1: Remove any manual `page()` calls for route changes. The integration handles this. * For scenario 2: Add `?type=preflight` to the server-side API call. The server gets the same response but does not persist events — the client SDK handles persistence. ### Insights data is missing * **Symptoms**: Personalization works, but the Insights dashboards doesn't display any data. Experiment results are empty. * **Cause**: Component view tracking requires the Insights plugin running in the browser. If you have a server-only setup with no client SDK, viewport-based tracking cannot work. * **Solution**: Ensure `@ninetailed/experience.js-plugin-insights` is installed and added to the provider's plugin list. If you are running a server-only setup, understand that experiment measurement requires a client-side component. This is a fundamental limitation, not a configuration issue. ### Wrong analytics plugin installed * **Symptoms**: You expected component insights and experiment measurement, but nothing appears. * **Cause**: The project installed `@ninetailed/experience.js-plugin-analytics` (the base class) instead of `@ninetailed/experience.js-plugin-insights` (the tracking implementation). * **Solution**: Replace the analytics plugin with the Insights plugin: ```diff - import { NinetailedAnalyticsPlugin } from '@ninetailed/experience.js-plugin-analytics'; + import { NinetailedInsightsPlugin } from '@ninetailed/experience.js-plugin-insights'; ``` ## SSR and edge issues ### Progressive timeout increases * **Symptoms**: API response times get slower over days or weeks. What started as sub-100ms responses now regularly times out. * **Cause**: The server-side code sends `identify` events on every page request. Each identify with a `userId` triggers a profile merge operation, which adds a redirect pointer. Over time, these redirect chains grow linearly, and each subsequent request must traverse a longer chain. * **Solution**: Only send `identify` events at login, signup, or account-linking moments, not on every request. Use a session-level flag (for example, a cookie) to track whether identification has already happened in the current session. After you fix the code, existing chains will self-heal over several days as the system shortcuts them during traversal. ### Duplicate profiles for the same visitor * **Symptoms**: A visitor appears to have two separate profile IDs. Server-side and client-side show different profiles. * **Cause**: The `ntaid` cookie is being set from the request value instead of the API response. After profile merges or relocations, the canonical profile ID changes. If you set the cookie from the old request value, the client SDK finds a stale ID and creates a new profile. * **Solution**: Always set the `ntaid` cookie from `response.data.profile.id`, never from the incoming request cookie: ```diff - response.cookies.set('ntaid', request.cookies.get('ntaid')); + response.cookies.set('ntaid', apiResponse.data.profile.id, { + path: '/', + maxAge: 31536000, + sameSite: 'lax', + secure: true, + }); ``` ### Geo-based audiences not matching on the server * **Symptoms**: Audiences based on visitor location work client-side but never match on server-rendered or edge-rendered requests. * **Cause**: The server-side page event does not include the visitor's country code. Without `countryCode` in the event context, the Experience API cannot evaluate geographic audiences. * **Solution**: Include the visitor's country code from your edge platform's geo headers: | Platform | Country code source | | ------------------ | ------------------------------- | | Vercel Edge | `request.geo?.country` | | Cloudflare Workers | `request.cf?.country` | | Akamai EdgeWorkers | `request.userLocation?.country` | | Next.js Middleware | `request.geo?.country` | ## Other common issues ### Preview UI appears in production * **Symptoms**: The Personalization preview widget is visible to end users. * **Cause**: The Preview plugin is instantiated without an environment check. * **Solution**: Gate the Preview plugin behind an environment or preview mode check. Only instantiate it when you are in a development or preview context: ```tsx plugins={[ new NinetailedInsightsPlugin(), ...(isPreview ? [new NinetailedPreviewPlugin({ experiences, audiences })] : []), ]} ``` ### Flash of default content * **Symptoms**: The baseline content is briefly visible before the personalized variant appears. * **Cause**: This is expected behavior in client-side-only setups. The server sends static HTML with baseline content, and the SDK swaps it after hydration. > **Info** > > **Important**: > This is not a bug. If the flash is unacceptable, switch to a hybrid SSR or edge setup where the server renders personalized HTML before it reaches the browser. See [Edge and Server Side Rendering](/personalization/edge-and-server-side-rendering). > Solutions to common Contentful Personalization setup issues.