Choosing a Next.js migration path from experience.js
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/clientwhen needed. - Pages Router apps use
@contentful/optimization-nextjs/pages-routerand@contentful/optimization-nextjs/pages-router/server. - Non-Next or unsupported server-rendering shapes use
@contentful/optimization-nodeon 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, orntaid. - 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 ingetServerSideProps. - 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
- Classify the current router and legacy SSR/ESR surfaces.
- Choose the target package by the route that owns personalization.
- Decide which layer owns the first page event.
- Decide how profile continuity moves from
ntaidto the targetctfl-opt-aidpolicy. - 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/andgetServerSideProps. - 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.
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.