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.
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.
Gather these inputs:
_app.tsx.getServerSideProps path that uses SSR plugin behavior or ntaid.getServerSideProps.OptimizedEntry.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.
Use the target guide to create both bindings:
@contentful/optimization-nextjs/pages-router.@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 <OptimizedEntry entryId>, 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.
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 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.
Client features use the React Web runtime:
trackFlagView().Use Migrating experience.js plugins and preview for plugin-specific replacement.
Verify the server and browser handoff:
createRequestHandoff(context, options) runs in getServerSideProps on the
personalized page.pageProps.contentfulOptimization.handoff reaches OptimizationRoot in _app.tsx.initialPageEvent: 'skip', and the separate
tracker skips whenever that handoff is present.OptimizedEntry renders a variant or baseline.@ninetailed/experience.js-next, SSR plugin imports, ntaid, and legacy React
surfaces.| 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. |