> 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. # Choose a Next.js migration path from experience.js > Use this guide when a Next.js app uses `@ninetailed/experience.js-next`, `@ninetailed/experience.js-next-esr`, or SSR plugin behavior and you need to choose App Router, Pages Router, or a manual Node/Web hybrid target before changing code. ## Overview Use this guide when a Next.js app uses `@ninetailed/experience.js-next`, `@ninetailed/experience.js-next-esr`, or SSR plugin behavior and you need to choose App Router, Pages Router, or a manual Node/Web hybrid target before changing code. ## What changes Legacy Next.js surfaces mix React provider behavior, route tracking, SSR profile continuity, and ESR helpers. The Optimization SDK Suite splits the target by actual runtime: * App Router Server Components use `@contentful/optimization-nextjs/app-router/server`; bound Client Components use `@contentful/optimization-nextjs/app-router/client` when needed. * Pages Router apps use `@contentful/optimization-nextjs/pages-router` and `@contentful/optimization-nextjs/pages-router/server`. * Non-Next or unsupported server-rendering shapes use `@contentful/optimization-node` on the server plus Web or React Web in the browser. After choosing, follow the target migration guide instead of mixing router patterns. ## Before you migrate Gather these inputs: * Whether the app renders through `app/`, `pages/`, or both. * Use of `@ninetailed/experience.js-next`, `@ninetailed/experience.js-next-esr`, SSR plugin helpers, route trackers, or `ntaid`. * Where the first page event is emitted today: server, browser tracker, or both. * Where visitor identity is persisted and whether the browser must continue the same profile. * Whether the target route can be per-request dynamic. Use these terms consistently: * App Router means routes under `app/`, including Server Components and route handlers. * Pages Router means routes under `pages/`, especially pages personalized in `getServerSideProps`. * SSR plugin means legacy experience.js server-side profile and cookie behavior. * ESR means legacy edge-side rendering helpers from `@ninetailed/experience.js-next-esr`. * Manual Node/Web hybrid means the app uses the Node SDK on a custom server boundary and the Web or React Web SDK in the browser. ## Migration path 1. Classify the current router and legacy SSR/ESR surfaces. 2. Choose the target package by the route that owns personalization. 3. Decide which layer owns the first page event. 4. Decide how profile continuity moves from `ntaid` to the target `ctfl-opt-aid` policy. 5. Follow the selected runtime migration guide. ## Replace legacy surfaces ### Identify the current Next.js integration Classify the app by the route that renders personalized content: * Use the App Router path when personalized content lives in `app/` routes or Server Components. * Use the Pages Router path when personalized pages use `pages/` and `getServerSideProps`. * Use a manual Node/Web hybrid only when the app has a custom server-rendering boundary that the Next.js adapters do not cover. Do not treat unexported ESR middleware or selector source as supported import surfaces. Some ESR helper files exist in the legacy package source but are not exported from the package entry, so they are not supported import contracts. If the legacy integration depends on ESR helpers, prefer the App Router SDK when the route can move there; otherwise treat the replacement as a manual Node/Web handoff. ### Choose App Router, Pages Router, or manual hybrid Decide whether personalized first paint may be per-request dynamic before choosing the adapter. App Router server personalization reads request data and makes the affected route dynamic, so it is not compatible with routes that must stay SSG or ISR. Pages Router `getServerSideProps` is already per-request. A manual Node/Web hybrid has the same cache responsibility as any custom SSR path: never share personalized output across visitors. | Current app shape | Target | | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | App Router owns the personalized route | [App Router migration](/personalization/optimization-sdk/migrate-experiencejs-next-to-nextjs-app-router/) | | Pages Router and `getServerSideProps` own the personalized route | [Pages Router migration](/personalization/optimization-sdk/migrate-experiencejs-next-to-nextjs-pages-router/) | | Custom SSR outside the Next.js adapters | [Node, SSR, and ESR migration](/personalization/optimization-sdk/migrate-experiencejs-node-ssr-and-esr/), then Web or React Web browser migration | Use the highest-level adapter that matches the app. In an App Router request path, the server binding's nested `optimization.request` family owns request initialization, provider state handoff, and first-page tracking that a manual hybrid would otherwise need to rebuild. ### Route SSR and first page event ownership Avoid duplicate page evaluation. Legacy Next tracking emits page events on the first route and on route changes, while SSR helpers can also evaluate the first request. In the target App Router path, the no-argument request handler only forwards the original request URL and sanitized request context. The server binding's nested `optimization.request` family evaluates the request, creates the handoff, and gives its `NextAppAutoPageTracker` first-page-event ownership automatically. Mount that tracker inside `optimization.request.OptimizationRoot`; do not create or pass a handoff or `initialPageEvent` prop for this ordinary request-family path. In the target Pages Router path, bind the server SDK with `bindNextjsPagesRouterServerOptimization(config)` and call its returned `createRequestHandoff(context, options)` inside `getServerSideProps`. The returned handoff records accepted server evaluation as `handoff.initialPageEvent === 'skip'` and a server path that did not report the view as `'emit'`. Pass that Pages Router handoff to `OptimizationRoot`, which consumes the instruction. Its browser tracker uses the handoff's `initialPageEvent` value and continues to track later browser navigations. ### Route cookie and profile continuity Legacy continuity commonly used `ntaid`. Target Web, React Web, and Next.js browser/framework SDKs use `ctfl-opt-aid` for the SDK-owned anonymous profile cookie. In a manual Node/Web hybrid, the Node SDK only exports the `ANONYMOUS_ID_COOKIE` constant; app code must read, write, and clear that cookie and pass the profile ID through `forRequest({ profile })`. Decide whether migration resets visitor identity or whether the app reads the legacy cookie and writes the target continuity value as a one-time operational handoff. The target consent record remains app-owned. Do not reuse `__nt-consent__` as if it were an SDK contract. ## Validate the migration * The selected guide matches the route that renders personalized content. * Exactly one layer owns the first page event for the first route. * The App Router request tracker receives first-page-event ownership automatically; explicit paths set it intentionally. * Cookie and consent ownership are documented in app code before deleting legacy packages. ## Troubleshooting | Symptom | Check | | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | Both server and browser emit the first page event | Use the App Router nested request root and tracker together; reserve explicit `initialPageEvent` plumbing for manual or Pages Router paths. | | App Router route no longer behaves statically | Request-family personalization reads request data; use a public-permutation, static, or browser-only path if static output is required. | | ESR migration has no matching import | The legacy ESR package did not export every helper present in source; use the explicit App Router server entry point or a manual Node/Web hybrid. | ## Related guides * [Migrate experience.js Next.js to App Router](/personalization/optimization-sdk/migrate-experiencejs-next-to-nextjs-app-router/) * [Migrate experience.js Next.js to Pages Router](/personalization/optimization-sdk/migrate-experiencejs-next-to-nextjs-pages-router/) * [Migrate experience.js Node, SSR, and ESR](/personalization/optimization-sdk/migrate-experiencejs-node-ssr-and-esr/) * [Next.js App Router integration guide](/personalization/optimization-sdk/integrate-the-optimization-sdk-in-a-nextjs-app-router-app/) * [Next.js Pages Router integration guide](/personalization/optimization-sdk/integrate-the-optimization-sdk-in-a-nextjs-pages-router-app/) * [Profile synchronization between client and server](/personalization/optimization-sdk/profile-synchronization-between-client-and-server/) > Use this guide when a Next.js app uses `@ninetailed/experience.js-next`, `@ninetailed/experience.js-next-esr`, or SSR plugin behavior and you need to choose App Router, Pages Router, or a manual Node/Web hybrid target before changing code.