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.
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:
@contentful/optimization-nextjs/app-router/server; bound
Client Components use @contentful/optimization-nextjs/app-router/client when needed.@contentful/optimization-nextjs/pages-router and
@contentful/optimization-nextjs/pages-router/server.@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.
Gather these inputs:
app/, pages/, or both.@ninetailed/experience.js-next, @ninetailed/experience.js-next-esr, SSR plugin helpers,
route trackers, or ntaid.Use these terms consistently:
app/, including Server Components and route handlers.pages/, especially pages personalized in getServerSideProps.@ninetailed/experience.js-next-esr.ntaid to the target ctfl-opt-aid policy.Classify the app by the route that renders personalized content:
app/ routes or Server Components.pages/ and getServerSideProps.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.
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 |
Pages Router and getServerSideProps own the personalized route |
Pages Router migration |
| Custom SSR outside the Next.js adapters | Node, SSR, and ESR migration, 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.
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.
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.
| 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. |