> 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. # Migrate experience.js Next.js to the Pages Router SDK > Use this guide when a Pages Router app uses `@ninetailed/experience.js-next`, SSR plugin behavior, or legacy React surfaces and you want to move to the Optimization Pages Router SDK. ## Overview Use this guide when a Pages Router app uses `@ninetailed/experience.js-next`, SSR plugin behavior, or legacy React surfaces and you want to move to the Optimization Pages Router SDK. ## What changes The Pages Router target uses `@contentful/optimization-nextjs/pages-router` for browser components and `@contentful/optimization-nextjs/pages-router/server` for `getServerSideProps`. Server props own request evaluation and profile continuity; the browser root receives a request handoff and continues with React Web behavior. Start with the [Next.js Pages Router integration guide](/personalization/optimization-sdk/integrate-the-optimization-sdk-in-a-nextjs-pages-router-app/). ## Before you migrate Gather these inputs: * Provider and tracker placement in `_app.tsx`. * Every `getServerSideProps` path that uses SSR plugin behavior or `ntaid`. * Legacy React components, hooks, flags, and mapper-dependent entries. * Consent cookie, profile cookie, and initial page-event behavior. * Any analytics, privacy, preview, or insights plugins. ## Migration path 1. Confirm this app should use Pages Router through [Choose a Next.js migration path from experience.js](/personalization/optimization-sdk/choose-a-nextjs-migration-path-from-experience-js/). 2. Migrate authored Contentful entries when legacy mapper output is still required. See [Migrate an experience.js Contentful model to Optimization](/personalization/optimization-sdk/migrate-an-experiencejs-contentful-model-to-optimization/). 3. Create the Pages Router client and server bindings. 4. Replace SSR plugin profile and page evaluation in `getServerSideProps`. 5. Replace personalized rendering with the bound Pages Router `OptimizedEntry`. 6. Replace client-side extras through React Web behavior and the plugin migration guide. 7. Remove legacy Next, React, and plugin packages after imports are gone. ## Replace legacy surfaces ### Inventory legacy Pages Router wiring Record where the legacy provider and tracker mount, whether `onRouteChange` replaces default page calls, and which pages use SSR plugin helpers. Also record any code that reads or writes `ntaid`, because target profile continuity uses the SDK-owned `ctfl-opt-aid` cookie. ### Install and bind the Pages Router SDK Use the target guide to create both bindings: * Client binding helper from `@contentful/optimization-nextjs/pages-router`. * Server binding helper from `@contentful/optimization-nextjs/pages-router/server`. Mount the target `OptimizationRoot` and `NextPagesAutoPageTracker` in `_app.tsx`, passing `pageProps.contentfulOptimization.handoff` to the root. `contentfulOptimization` is an app-owned page props wrapper; its `handoff` field is the SDK `BrowserOptimizationHandoff` returned by the server binding's `createRequestHandoff(context, options)` helper. The handoff can contain browser consent defaults, request-scoped optimization state, managed entries, and the required `initialPageEvent` value. The root consumes the handoff's initial-page instruction. Keep the separate tracker mounted with `initialPageEvent={handoff ? 'skip' : 'emit'}` so it skips the first route whenever the root has a handoff and emits only when no handoff exists. If migrated components will use ``, configure the server binding with the app's `contentful` client. Pass `prefetchManagedEntries` descriptors—entry IDs or objects containing `entryId` and optional `entryQuery`—in the `options` passed to `createRequestHandoff(context, options)`. The helper fetches those baselines and adds them to `handoff.entries` before the props reach the root. The Pages Router client binding does not fetch managed entries by itself. ### Replace server profile and page evaluation In an app-owned server module, call `bindNextjsPagesRouterServerOptimization(config)` once and destructure its returned `createRequestHandoff` helper. Call `createRequestHandoff(context, options)` inside `getServerSideProps`, then assign the returned handoff to the app-owned `contentfulOptimization.handoff` prop. If your app wraps this sequence in a helper, define that helper in the server module before importing it into a page. The server binding resolves request consent, emits the first page event when allowed, writes the anonymous-id cookie when profile persistence permits it, and returns the request handoff. Observe accepted server evaluation by checking `contentfulOptimization.handoff.initialPageEvent === 'skip'`; observe denied consent by checking that no Experience API call is made and the value is `'emit'`. Pass the handoff to `OptimizationRoot`. The root follows its `initialPageEvent` value, while `NextPagesAutoPageTracker` uses `initialPageEvent={handoff ? 'skip' : 'emit'}` to avoid duplicating the root's first-route decision. Keep legacy route-change code removed. ### Replace personalized rendering Replace legacy React wrappers and mapper output with the Pages Router `OptimizedEntry`. It accepts a manual `baselineEntry` or an `entryId` path backed by baselines in `handoff.entries`. Create those entries by passing `prefetchManagedEntries` descriptors in the `options` argument to `createRequestHandoff(context, options)`. It can use per-entry loading, error, and live-update props because it is the React Web component bound for Pages Router. If the first render depends on legacy `nt_*` fields, migrate the Contentful model before replacing the component. ### Replace client-side extras Client features use the React Web runtime: * Flags use target flag reads and optional `trackFlagView()`. * Analytics vendors use accepted and blocked event streams. * Consent uses app-owned policy passed to server and browser SDK surfaces. * Preview attaches to the live browser SDK through the preview panel package. Use [Migrate experience.js plugins and preview](/personalization/optimization-sdk/migrate-experiencejs-plugins-and-preview/) for plugin-specific replacement. ### Validate Pages Router migration Verify the server and browser handoff: * The server binding's `createRequestHandoff(context, options)` runs in `getServerSideProps` on the personalized page. * The app-owned `pageProps.contentfulOptimization.handoff` reaches `OptimizationRoot` in `_app.tsx`. * The handoff records accepted server evaluation with `initialPageEvent: 'skip'`, and the separate tracker skips whenever that handoff is present. * A target `OptimizedEntry` renders a variant or baseline. * Personalized results are not cached outside the request boundary. ## Validate the migration * Search for `@ninetailed/experience.js-next`, SSR plugin imports, `ntaid`, and legacy React surfaces. * Verify accepted server evaluation and denied-consent behavior. * Verify all-locale Contentful payloads are not used for optimized entries. * Verify client-side plugin replacements only after the route and rendering work. ## Troubleshooting | Symptom | Check | | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | First page events duplicate | Pass the handoff to `OptimizationRoot`; set `NextPagesAutoPageTracker` to `initialPageEvent={handoff ? 'skip' : 'emit'}`. | | `getServerSideProps` returns a 500 on API failure | Wrap the server helper and render baseline on failure when your app needs graceful fallback. | | Browser render cannot find managed entries | Pass `prefetchManagedEntries` descriptors in the `options` argument to `createRequestHandoff(context, options)`. | | Hooks import fails | Import React Web hooks from `@contentful/optimization-nextjs/client`, not `/pages-router`. | ## Related guides * [Next.js Pages Router integration guide](/personalization/optimization-sdk/integrate-the-optimization-sdk-in-a-nextjs-pages-router-app/) * [Choose a Next.js migration path from experience.js](/personalization/optimization-sdk/choose-a-nextjs-migration-path-from-experience-js/) * [Migrate an experience.js Contentful model to Optimization](/personalization/optimization-sdk/migrate-an-experiencejs-contentful-model-to-optimization/) * [Migrate experience.js plugins and preview](/personalization/optimization-sdk/migrate-experiencejs-plugins-and-preview/) * [Profile synchronization between client and server](/personalization/optimization-sdk/profile-synchronization-between-client-and-server/) * [Pages Router reference implementation](https://github.com/contentful/optimization/blob/optimization-swift-v2.0.1/implementations/nextjs-sdk_pages-router/README.md) > Use this guide when a Pages Router app uses `@ninetailed/experience.js-next`, SSR plugin behavior, or legacy React surfaces and you want to move to the Optimization Pages Router SDK.