Browser-only route
+ Next view +diff --git a/.github/workflows/main-pipeline.yaml b/.github/workflows/main-pipeline.yaml
index 68527233a..f28dbed8a 100644
--- a/.github/workflows/main-pipeline.yaml
+++ b/.github/workflows/main-pipeline.yaml
@@ -1435,22 +1435,27 @@ jobs:
disk-size: 8G
disable-animations: true
emulator-options: -no-window -no-audio -no-boot-anim -gpu swiftshader_indirect
- script: |
- echo "Verifying JAVA_HOME: $JAVA_HOME"
- java -version
- echo "Setting up adb reverse port forwarding..."
- adb reverse tcp:8000 tcp:8000
- echo "Running E2E tests..."
- pnpm run implementation:react-native-sdk -- test:e2e:android:run:release -- --loglevel error --retries 1 --shard=${{ matrix.shard }}/2
-
- - name: Capture Android E2E diagnostics on failure
- if: failure()
+ # The action runs each script line separately and tears down the emulator before returning.
+ # Keep tests and failure-time adb diagnostics in one shell command so adb can still reach it.
+ script: >-
+ set -e;
+ echo "Verifying JAVA_HOME: $JAVA_HOME";
+ java -version;
+ echo "Setting up adb reverse port forwarding...";
+ adb reverse tcp:8000 tcp:8000;
+ echo "Running E2E tests...";
+ if pnpm run implementation:react-native-sdk -- test:e2e:android:run:release -- --loglevel error --retries 1 --shard=${{ matrix.shard }}/2; then exit 0; else test_status=$?; fi;
+ mkdir -p /tmp/react-native-e2e-diagnostics || true;
+ timeout --signal=TERM --kill-after=5s 10s adb reverse --list > /tmp/react-native-e2e-diagnostics/adb-reverse-list.txt 2>&1 || true;
+ timeout --signal=TERM --kill-after=5s 20s adb logcat -d -v threadtime > /tmp/react-native-e2e-diagnostics/android-logcat.txt 2>&1 || true;
+ exit "$test_status"
+
+ - name: Capture host E2E diagnostics
+ if: always()
run: |
echo "=== Mock Server Logs ==="
cat /tmp/mock-server.log || echo "No mock server logs found"
mkdir -p /tmp/react-native-e2e-diagnostics
- adb reverse --list > /tmp/react-native-e2e-diagnostics/adb-reverse-list.txt 2>&1 || true
- adb logcat -d -v threadtime > /tmp/react-native-e2e-diagnostics/android-logcat.txt 2>&1 || true
find /tmp -maxdepth 1 -name '*.detox.jsonl' -exec cp {} /tmp/react-native-e2e-diagnostics/ \; 2>/dev/null || true
- name: Stop Mock Server
diff --git a/AGENTS.md b/AGENTS.md
index 182e8ecdc..43104b32d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -271,6 +271,14 @@ explicitly requests it.
impact crosses package families or is uncertain, use aggregate `pnpm size:report` for the complete
inventory, then `pnpm size:check` after classification.
- On failure, report the command, package or bundle, budget, actual size, and delta.
+- For an explicitly approved increase to an exceeded budget, or a request to normalize budgets,
+ set each budget in the authorized scope to `ceil((measuredGzipBytes + 100) / 100) * 100`: add
+ 100 bytes to the measured gzip size, then round up to a multiple of 100 bytes. Use the
+ completed build's `size:report` measurement, including reachable local chunks/assets counted by the
+ checker; do not calculate from the previous budget or an isolated entry file. Normalization
+ applies to every requested entry, including budgets that decrease or already satisfy their
+ limit. This calculation does not authorize additional budget changes or waive the approval,
+ failure-classification, scope, and validation rules in this section.
- Allowed remediation is limited to confirmed current-change regressions, dependency/import
mistakes, and measurement issues. Every remediation must preserve maintainability: do not make
code harder to read, type, test, debug, or change just to reduce bytes. Prefer fixes such as
diff --git a/README.md b/README.md
index f20bc0038..50acbd48b 100644
--- a/README.md
+++ b/README.md
@@ -70,17 +70,17 @@ that surface.
The published package surface is intentionally layered. The table below is a package inventory and
high-level role summary.
-| Package | Kind | Runtime | Role | Package README |
-| -------------------------------------------- | --------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------ |
-| `@contentful/optimization-web` | Environment SDK | Browser | Stateful browser SDK for optimization, tracking, and consent | [Web SDK](./packages/web/web-sdk/README.md) |
-| `@contentful/optimization-react-web` | Framework SDK | React on the web | React integration layer on top of the Web SDK | [React Web SDK](./packages/web/frameworks/react-web-sdk/README.md) |
-| `@contentful/optimization-nextjs` | Framework SDK | Next.js | Next.js adapter for explicit App Router and Pages Router bound components, SSR, client tracking, and request composition | [Next.js SDK](./packages/web/frameworks/nextjs-sdk/README.md) |
-| `@contentful/optimization-node` | Environment SDK | Node.js | Stateless Node SDK for server-side and SSR integrations | [Node SDK](./packages/node/node-sdk/README.md) |
-| `@contentful/optimization-react-native` | Environment SDK | React Native | React Native SDK for mobile applications | [React Native SDK](./packages/react-native-sdk/README.md) |
-| `@contentful/optimization-web-preview-panel` | Tooling package | Browser | Preview tooling package for existing Web SDK instances | [Web Preview Panel](./packages/web/preview-panel/README.md) |
-| `@contentful/optimization-core` | Shared SDK foundation | Runtime-agnostic | Shared optimization foundation, CDA schemas, and aggregate schema pass-throughs for runtime adapters and SDK layers | [Core SDK](./packages/universal/core-sdk/README.md) |
-| `@contentful/optimization-api-client` | Universal library | Runtime-agnostic | Direct Experience API and Insights API transport with their validation schemas | [API Client](./packages/universal/api-client/README.md) |
-| `@contentful/optimization-api-schemas` | Compatibility facade | Runtime-agnostic | Deprecated compatibility facade for the historical combined API-schema root surface | [API Schemas](./packages/universal/api-schemas/README.md) |
+| Package | Kind | Runtime | Role | Package README |
+| -------------------------------------------- | --------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
+| `@contentful/optimization-web` | Environment SDK | Browser | Stateful browser SDK for optimization, tracking, and consent | [Web SDK](./packages/web/web-sdk/README.md) |
+| `@contentful/optimization-react-web` | Framework SDK | React on the web | React integration layer on top of the Web SDK | [React Web SDK](./packages/web/frameworks/react-web-sdk/README.md) |
+| `@contentful/optimization-nextjs` | Framework SDK | Next.js | Next.js adapter for request preview handoff, browser queue commitment, SSR, and router composition | [Next.js SDK](./packages/web/frameworks/nextjs-sdk/README.md) |
+| `@contentful/optimization-node` | Environment SDK | Node.js | Stateless Node SDK for server-side and SSR integrations | [Node SDK](./packages/node/node-sdk/README.md) |
+| `@contentful/optimization-react-native` | Environment SDK | React Native | React Native SDK for mobile applications | [React Native SDK](./packages/react-native-sdk/README.md) |
+| `@contentful/optimization-web-preview-panel` | Tooling package | Browser | Preview tooling package for existing Web SDK instances | [Web Preview Panel](./packages/web/preview-panel/README.md) |
+| `@contentful/optimization-core` | Shared SDK foundation | Runtime-agnostic | Shared optimization foundation, CDA schemas, and aggregate schema pass-throughs for runtime adapters and SDK layers | [Core SDK](./packages/universal/core-sdk/README.md) |
+| `@contentful/optimization-api-client` | Universal library | Runtime-agnostic | Direct Experience API and Insights API transport with their validation schemas | [API Client](./packages/universal/api-client/README.md) |
+| `@contentful/optimization-api-schemas` | Compatibility facade | Runtime-agnostic | Deprecated compatibility facade for the historical combined API-schema root surface | [API Schemas](./packages/universal/api-schemas/README.md) |
General selection rules:
@@ -133,10 +133,10 @@ patterns with intentionally minimal application code.
SDK
- [Node SSR + Web Vanilla](./implementations/node-sdk+web-sdk/README.md) - split server/browser flow
using Node and Web SDKs together
-- [Next.js SDK App Router](./implementations/nextjs-sdk_app-router/README.md) - Next.js App Router
- first-paint SSR with browser-side takeover after hydration
-- [Next.js SDK Pages Router](./implementations/nextjs-sdk_pages-router/README.md) - Next.js Pages
- Router integration using `getServerSideProps` state handoff
+- [Next.js SDK App Router](./implementations/nextjs-sdk_app-router/README.md) - request-preview SSR
+ with browser queue commitment after hydration
+- [Next.js SDK Pages Router](./implementations/nextjs-sdk_pages-router/README.md) - Pages Router
+ integration using `getServerSideProps` prepared handoff
- [React Native](./implementations/react-native-sdk/README.md) - mobile application integration for
Android and iOS targets
- [iOS Reference App](./implementations/ios-sdk/README.md) - native app and XCUITest surface for
diff --git a/documentation/authoring/blueprints/nextjs-app-router.md b/documentation/authoring/blueprints/nextjs-app-router.md
index 7e3eecb75..3d280688b 100644
--- a/documentation/authoring/blueprints/nextjs-app-router.md
+++ b/documentation/authoring/blueprints/nextjs-app-router.md
@@ -53,23 +53,23 @@ guide: ../../guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app.
## Section map
-| Section | Category | Purpose | Must teach or show | Fact sources |
-| ------------------------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| How the SDK fits your app | Required for first integration | Orient the reader to the explicit server/client entry points and binding boundaries before deeper configuration. | `/app-router/server` with `bindNextjsAppRouterServerOptimization` for Server Components; `/app-router/client` with `bindNextjsAppRouterClientOptimization` only for bound Client Components; router-neutral `/client` for hooks and per-entry browser controls; no conditional `/app-router` path; separate app-local server and client binding modules so imports cannot cross runtime boundaries; which initial settings are SDK mechanism versus application policy. Keep that normal path first, then teach one atomic optional request-family change with `beforeInitialPage`: define the initial page decision and server event -> handoff -> live browser runtime -> callback -> direct page attempt or same-route handoff skip -> attempted-route mark -> later-route timeline; capture the callback and export `RequestOptimizationRoot` from a `'use client'` module; inject that component reference through the server binder's `request.OptimizationRoot` option; immediately remove the request tracker export, layout import, and JSX before the reader can stop; show multiple awaited operations represented by one returned Promise and define retained lifetime, after-live-owned-runtime invocation, event delegates, Promise/thenable, and early identity; keep the callback and lazy payload builder in the client module rather than Flight/server config; distinguish the auto-routed request root from the direct client `OptimizationRoot`, which still requires explicit `routeKey` and `buildPagePayload` and rejects eager `initialPagePayload`; state the optional-`never` server-config boundary and unchanged serialized handoff shape. | [package](../../internal/sdk-knowledge/web/nextjs-app-router.md#package--entry-points), [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [React Web setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding) |
-| Choose who owns entry fetching | Required for first integration | Establish the app-owned-versus-SDK-managed entry boundary before personalization. | Concise executable app-owned `baselineEntry` and SDK-managed examples; both workflows are supported and neither is framed as historical; the final managed example shows `contentful: { client }`, `prefetchManagedEntries={[entryId]}`, and request `OptimizedEntry entryId={entryId}` together; include an object `managedEntry` alternative; only SDK-owned CDA work receives direct request/CDA overlap; the SDK owns managed cache/dedup and the one handoff merge, so consumers add no duplicate await, cache, or performance option; fixed SDK property names versus app/model values; server-render versus root-prefetch/browser-handoff boundary. | [App Router setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [entry-source boundary](../../internal/sdk-knowledge/shared/concepts.md#entry-source-boundary-managed-or-manual), [entry resolution](../../internal/sdk-knowledge/shared/concepts.md#entry-resolution) |
-| Request context and the profile cookie | Common but policy-dependent | Teach how the SDK-owned request family receives visitor context and where application policy enters. | The no-argument forwarding handler as the default, the verified narrow matcher, the Next.js 16 `proxy.ts`/`proxy` and Next.js 13 to 15 `middleware.ts`/`middleware` support paths, and the forwarded request URL requirement; `consent.server` and cookie ownership; distinguish the SDK-owned anonymous ID cookie from full profile, selected state, and app-owned consent; define route key, initial page payload, hydration mode, and private-request handoff; trusted response-capable request persistence only as an advanced opt-in; one cached SDK initializer shared by every `optimization.request` wrapper; no app-owned header/cookie/URL/route-key/page-payload plumbing; plus a server-observable verification path. | [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [identifiers](../../internal/sdk-knowledge/web/nextjs-app-router.md#identifier-ownership), [events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking), [experience response](../../internal/sdk-knowledge/shared/concepts.md#experience-response-payload), [consent](../../internal/sdk-knowledge/web/nextjs-app-router.md#consent--persistence), [runtime quirks](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks) |
-| Personalizing first paint on the server | Required for first integration | Show the request-bound entry render that produces Milestone 1. | A representative renderer diff using `optimization.request.OptimizedEntry`; one skeleton union and content-type narrowing; baseline behavior; the shared initialization guarantee even when an entry starts before the root; managed baseline work starts concurrently with request initialization while selected state remains behind both; and the dynamic-render and private-request cache consequences. | [rendering](../../internal/sdk-knowledge/web/nextjs-app-router.md#render--entry-resolution), [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [runtime quirks](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks), [fallback](../../internal/sdk-knowledge/shared/concepts.md#baseline-fallback) |
-| The bound root and page events | Required for first integration | Explain SDK-owned request handoff and ownership of the first page event. | Keep the normal `optimization.request.OptimizationRoot` plus `optimization.request.NextAppAutoPageTracker` path first: request-independent public chrome outside the private root and `Suspense`; all provider-dependent UI and the tracker inside; meaningful fallback; `Link` prefetch retained; request-family handoff, route, initial payload, and first-event ownership; mounted accepted/blocked diagnostic. Then deepen the atomic alternative with `beforeInitialPage`: the injected `ClientRequestOptimizationRoot` replaces the default browser root for the request family, receives only serializable handoff/hydration/defaults/children, derives route and lazy payload from the non-emitting App Router inputs hook, and has no separate tracker. Define direct attempt, emitter, initial `skip`, and `{ accepted: false }`; teach callback -> latest direct page attempt or same-route handoff skip -> attempted-route initial `skip` mark -> later-route `emit`, watchdog/error continuation only while mounted on the current live runtime, best-effort and verified in-flight route-interleaving limits, visible five-second fallback/freeze with live-update remedy, and a performable accepted/blocked ordering/duplicate-page check. Qualify every later tracker example, production check, and troubleshooting row with normal tracker mode versus `beforeInitialPage` mode; route Optional readers here without a new section; preserve provider/analytics exclusions and explicit top-level static/public-permutation/analytics roots. | [components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking), [React Web setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding), [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [React Web failure](../../internal/sdk-knowledge/web/react-web.md#failure--fallback-behavior), [Web observables](../../internal/sdk-knowledge/web/web.md#consent--persistence) |
-| Browser takeover and live updates | Required for first integration | Explain the handoff mental model every integration uses while marking re-personalization as opt-in. | Snapshot-to-live behavior, opt-in controls, a browser example with an observable before/after state change, and provider ownership. | [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [React Web setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding), [React Web runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks), [live updates](../../internal/sdk-knowledge/shared/concepts.md#live-updates) |
-| Entry interaction tracking | Common but policy-dependent | Make default interaction instrumentation and policy controls visible. | Tracked interactions, attribution, binding and per-entry controls, consent/profile consequences, and one performable check. | [App Router events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking), [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [shared consent](../../internal/sdk-knowledge/shared/concepts.md#consent--persistence) |
-| Consent, identity, profile, and reset | Common but policy-dependent | Replace the quick-start consent shortcut with application-owned policy and identity flows. | A production consent flow shared by server and browser, identity/reset controls, and clear SDK-versus-application cleanup ownership. | [App Router consent](../../internal/sdk-knowledge/web/nextjs-app-router.md#consent--persistence), [identifiers](../../internal/sdk-knowledge/web/nextjs-app-router.md#identifier-ownership), [React Web consent/actions](../../internal/sdk-knowledge/web/react-web.md#consent--persistence), [Web failure behavior](../../internal/sdk-knowledge/web/web.md#failure--fallback-behavior) |
-| Analytics forwarding | Optional | Teach the safe browser forwarding seam, then route vendor-specific setup to the supplemental guide. | A lifecycle-safe subscription with consent gating, deduplication and failure handling, plus the supplemental-guide route. | [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [React Web consent](../../internal/sdk-knowledge/web/react-web.md#consent--persistence), [Web consent/observables](../../internal/sdk-knowledge/web/web.md#consent--persistence) |
-| Merge tags and Custom Flags | Optional | Cover content-model-dependent personalization beyond entry replacement. | One complete Rich Text merge-tag example, the alternate hook route, and one observable Custom Flag example with authoring links. | [App Router components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [React Web rendering](../../internal/sdk-knowledge/web/react-web.md#render--entry-resolution), [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [shared entry resolution](../../internal/sdk-knowledge/shared/concepts.md#entry-resolution) |
-| Preview panel | Optional | Provide an environment-gated authoring and debugging path. | Installation and attach flow, readiness/environment/error handling, browser-safety considerations, and a forced-variant check. | [React Web runtime quirks](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks), [Web fallback/preview](../../internal/sdk-knowledge/web/web.md#failure--fallback-behavior) |
-| Route-level SSR, browser takeover, and browser-owned islands | Advanced or production-only | Help mature applications choose ownership per route. | A decision table comparing first-paint, browser, event, cache ownership, and tradeoffs; teach the private-slot composition as the preferred request-performance shape, with public request-independent chrome outside the request root and `Suspense`, meaningful fallback, provider-dependent UI inside, and version-appropriate `connection()` handling: call it at the Cache Components private request boundary on Next.js 15 and later, but omit it on Next.js 13 to 14; preserve the independent hidden-until-ready browser hydration scenario; use the nested request family only for private request rendering, the top-level `/app-router/server` family for explicit static/public/analytics handoffs, and `/app-router/client` or router-neutral `/client` for browser-owned surfaces; route full static/ISR/edge recipes to the supplemental guide. | [App Router components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [App Router runtime quirks](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks), [live updates](../../internal/sdk-knowledge/shared/concepts.md#live-updates), [handoff](../../internal/sdk-knowledge/shared/concepts.md#optimization-handoff) |
-| Manual server and client escape hatches | Advanced or production-only | Expose lower-level APIs only after the request-family path is understood. | A precise API and ownership map; keep bound `createRequestHandoff()` and lower-level `/server` request APIs as advanced orchestration paths; keep trusted response-capable handler persistence here as an explicit opt-in while forwarding-only remains the default; show a complete manual request-to-provider flow only when the guide recommends that path; do not present manual request plumbing as the private-request default. | [package and setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#package--entry-points), [components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking), [runtime quirks](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks) |
-| Caching and request deduplication | Advanced or production-only | Prevent profile-specific output from entering shared caches without recreating SDK request plumbing. | A cache-safety table; the request family's one-initialization-per-RSC-request contract and separation across requests; the distinction between SDK initialization sharing, managed cache/dedup, the single handoff merge, and shared output caching; managed baseline fetching/prefetch overlaps request initialization automatically while selected state waits for both; no app-owned request cache, duplicate awaits, or performance option; links to the handoff concept and supplemental rendering guide; and a two-profile validation. | [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [runtime quirks](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks), [managed fetching](../../internal/sdk-knowledge/shared/concepts.md#entry-source-boundary-managed-or-manual), [handoff](../../internal/sdk-knowledge/shared/concepts.md#optimization-handoff) |
-| Strict consent and duplicate-event controls | Advanced or production-only | Turn privacy and duplicate-event policy into explicit production decisions. | A strict event-policy example, blocked-event diagnostics, an accepted local page-event check triggered by a normal participating Next.js `Link` navigation after consent, first-event ownership, and the separate steps in consent withdrawal. | [App Router consent](../../internal/sdk-knowledge/web/nextjs-app-router.md#consent--persistence), [App Router events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking), [React Web consent](../../internal/sdk-knowledge/web/react-web.md#consent--persistence), [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [Web failure behavior](../../internal/sdk-knowledge/web/web.md#failure--fallback-behavior) |
+| Section | Category | Purpose | Must teach or show | Fact sources |
+| ------------------------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| How the SDK fits your app | Required for first integration | Orient the reader to the explicit server/client entry points and binding boundaries before deeper configuration. | `/app-router/server` with `bindNextjsAppRouterServerOptimization` for Server Components; `/app-router/client` with `bindNextjsAppRouterClientOptimization` only for bound Client Components; router-neutral `/client` for hooks and per-entry browser controls; no conditional `/app-router` path; separate app-local server and client binding modules so imports cannot cross runtime boundaries; which initial settings are SDK mechanism versus application policy. Keep that normal path first, then teach optional browser-owned `beforeInitialPage` sequencing. Keep paired request `initialEvents` primary; every valid, consent-eligible request handoff contains replay even when preview fails, and its request-family root bypasses `beforeInitialPage`. Do not inject a callback-enabled root into the server request family. On a route with no handoff, use the direct `/app-router/client` binder root; use public Next.js `usePathname` and `useSearchParams` hooks to build stable `routeKey` as pathname plus search and lazy `buildPagePayload` from those route values; keep `useSearchParams` under a meaningful `Suspense` fallback, let the root own initial and later page tracking, and mount no separate tracker. Capture the callback and export the direct `OptimizationRoot` from a 'use client' module; do not inject it through the server binder or mount another tracker; show multiple awaited operations represented by one returned Promise and define retained lifetime, after-live-owned-runtime invocation, event delegates, Promise/thenable, and early identity; keep the callback and lazy payload builder in the client module rather than Flight/server config; distinguish the auto-routed request root from the direct client `OptimizationRoot`, which still requires explicit `routeKey` and `buildPagePayload` and rejects eager `initialPagePayload`; state the optional-`never` server-config boundary and unchanged serialized handoff shape. | [package](../../internal/sdk-knowledge/web/nextjs-app-router.md#package--entry-points), [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [React Web setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding) |
+| Choose who owns entry fetching | Required for first integration | Establish the app-owned-versus-SDK-managed entry boundary before personalization. | Concise executable app-owned `baselineEntry` and SDK-managed examples; both workflows are supported and neither is framed as historical; the final managed example shows `contentful: { client }`, `prefetchManagedEntries={[entryId]}`, and request `OptimizedEntry entryId={entryId}` together; include an object `managedEntry` alternative; only SDK-owned CDA work receives direct request/CDA overlap; the SDK owns managed cache/dedup and the one handoff merge, so consumers add no duplicate await, cache, or performance option; fixed SDK property names versus app/model values; server-render versus root-prefetch/browser-handoff boundary. | [App Router setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [entry-source boundary](../../internal/sdk-knowledge/shared/concepts.md#entry-source-boundary-managed-or-manual), [entry resolution](../../internal/sdk-knowledge/shared/concepts.md#entry-resolution) |
+| Request context and the profile cookie | Common but policy-dependent | Teach how the SDK-owned request family receives visitor context and where application policy enters. | The no-argument forwarding handler as the default, the verified narrow matcher, the Next.js 16 `proxy.ts`/`proxy` and Next.js 13 to 15 `middleware.ts`/`middleware` support paths, and the forwarded request URL requirement; `consent.server` and cookie ownership; distinguish the SDK-owned anonymous ID cookie from full profile, selected state, and app-owned consent; define route key, initial page payload, hydration mode, and private-request handoff; trusted response-capable request persistence only as an advanced opt-in; one cached SDK initializer shared by every `optimization.request` wrapper; no app-owned header/cookie/URL/route-key/page-payload plumbing; plus a server-observable verification path. | [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [identifiers](../../internal/sdk-knowledge/web/nextjs-app-router.md#identifier-ownership), [events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking), [experience response](../../internal/sdk-knowledge/shared/concepts.md#experience-response-payload), [consent](../../internal/sdk-knowledge/web/nextjs-app-router.md#consent--persistence), [runtime quirks](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks) |
+| Personalizing first paint on the server | Required for first integration | Show the request-bound entry render that produces Milestone 1. | A representative renderer diff using `optimization.request.OptimizedEntry`; one skeleton union and content-type narrowing; baseline behavior; the shared initialization guarantee even when an entry starts before the root; managed baseline work starts concurrently with request initialization while selected state remains behind both; and the dynamic-render and private-request cache consequences. | [rendering](../../internal/sdk-knowledge/web/nextjs-app-router.md#render--entry-resolution), [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [runtime quirks](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks), [fallback](../../internal/sdk-knowledge/shared/concepts.md#baseline-fallback) |
+| The bound root and page events | Required for first integration | Explain SDK-owned request handoff and ownership of the first page event. | Keep the normal `optimization.request.OptimizationRoot` plus `optimization.request.NextAppAutoPageTracker` path first: request-independent public chrome outside the private root and `Suspense`; all provider-dependent UI and the tracker inside; meaningful fallback; `Link` prefetch retained. Explain that request initialization prepares the initial event batch and preflights it per request for server rendering; the server's preview is not event commitment. The browser tracker passes the handoff to the Web SDK, which admits the prepared batch as one submission to its existing Experience queue; queue admission is reported independently of HTTP delivery, and a matching replay prevents a second ordinary page submission. Include route, initial payload, and first-event ownership; a mounted accepted/blocked diagnostic. Then deepen the atomic alternative with `beforeInitialPage`: the direct root from the `/app-router/client` binder runs on a browser-owned route with no handoff, reads pathname and search with public Next.js `usePathname` and `useSearchParams` hooks to supply stable `routeKey` and lazy `buildPagePayload`, and owns page tracking without a separate tracker. Do not use the injected request-family root for this callback: valid, consent-eligible request handoffs contain replay even when preview fails and bypass `beforeInitialPage`. For this no-handoff path, define direct attempt, emitter, initial `skip`, and `{ accepted: false }`; teach callback -> latest direct page attempt -> attempted-route initial `skip` mark -> later-route `emit`, watchdog/error continuation only while mounted on the current live runtime, best-effort and verified in-flight route-interleaving limits, visible five-second fallback/freeze with live-update remedy, and a performable accepted/blocked ordering/duplicate-page check. Qualify every later tracker example, production check, and troubleshooting row with normal tracker mode versus `beforeInitialPage` mode; route Optional readers here without a new section; preserve provider/analytics exclusions and explicit top-level static/public-permutation/analytics roots. | [components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking), [React Web setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding), [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [React Web failure](../../internal/sdk-knowledge/web/react-web.md#failure--fallback-behavior), [Web observables](../../internal/sdk-knowledge/web/web.md#consent--persistence) |
+| Browser takeover and live updates | Required for first integration | Explain the handoff mental model every integration uses while marking re-personalization as opt-in. | Snapshot-to-live behavior, opt-in controls, a browser example with an observable before/after state change, and provider ownership. | [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [React Web setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding), [React Web runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks), [live updates](../../internal/sdk-knowledge/shared/concepts.md#live-updates) |
+| Entry interaction tracking | Common but policy-dependent | Make default interaction instrumentation and policy controls visible. | Tracked interactions, attribution, binding and per-entry controls, consent/profile consequences, and one performable check. | [App Router events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking), [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [shared consent](../../internal/sdk-knowledge/shared/concepts.md#consent--persistence) |
+| Consent, identity, profile, and reset | Common but policy-dependent | Replace the quick-start consent shortcut with application-owned policy and identity flows. | A production consent flow shared by server and browser, identity/reset controls, and clear SDK-versus-application cleanup ownership. | [App Router consent](../../internal/sdk-knowledge/web/nextjs-app-router.md#consent--persistence), [identifiers](../../internal/sdk-knowledge/web/nextjs-app-router.md#identifier-ownership), [React Web consent/actions](../../internal/sdk-knowledge/web/react-web.md#consent--persistence), [Web failure behavior](../../internal/sdk-knowledge/web/web.md#failure--fallback-behavior) |
+| Analytics forwarding | Optional | Teach the safe browser forwarding seam, then route vendor-specific setup to the supplemental guide. | A lifecycle-safe subscription with consent gating, deduplication and failure handling, plus the supplemental-guide route. | [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [React Web consent](../../internal/sdk-knowledge/web/react-web.md#consent--persistence), [Web consent/observables](../../internal/sdk-knowledge/web/web.md#consent--persistence) |
+| Merge tags and Custom Flags | Optional | Cover content-model-dependent personalization beyond entry replacement. | One complete Rich Text merge-tag example, the alternate hook route, and one observable Custom Flag example with authoring links. | [App Router components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [React Web rendering](../../internal/sdk-knowledge/web/react-web.md#render--entry-resolution), [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [shared entry resolution](../../internal/sdk-knowledge/shared/concepts.md#entry-resolution) |
+| Preview panel | Optional | Provide an environment-gated authoring and debugging path. | Installation and attach flow, readiness/environment/error handling, browser-safety considerations, and a forced-variant check. | [React Web runtime quirks](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks), [Web fallback/preview](../../internal/sdk-knowledge/web/web.md#failure--fallback-behavior) |
+| Route-level SSR, browser takeover, and browser-owned islands | Advanced or production-only | Help mature applications choose ownership per route. | A decision table comparing first-paint, browser, event, cache ownership, and tradeoffs; teach the private-slot composition as the preferred request-performance shape, with public request-independent chrome outside the request root and `Suspense`, meaningful fallback, provider-dependent UI inside, and version-appropriate `connection()` handling: call it at the Cache Components private request boundary on Next.js 15 and later, but omit it on Next.js 13 to 14; preserve the independent hidden-until-ready browser hydration scenario; use the nested request family only for private request rendering, the top-level `/app-router/server` family for explicit static/public/analytics handoffs, and `/app-router/client` or router-neutral `/client` for browser-owned surfaces; route full static/ISR/edge recipes to the supplemental guide. | [App Router components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [App Router runtime quirks](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks), [live updates](../../internal/sdk-knowledge/shared/concepts.md#live-updates), [handoff](../../internal/sdk-knowledge/shared/concepts.md#optimization-handoff) |
+| Manual server and client escape hatches | Advanced or production-only | Expose lower-level APIs only after the request-family path is understood. | A precise API and ownership map; keep bound `createRequestHandoff()` and lower-level `/server` request APIs as advanced orchestration paths; keep trusted response-capable handler persistence here as an explicit opt-in while forwarding-only remains the default; show a complete manual request-to-provider flow only when the guide recommends that path; do not present manual request plumbing as the private-request default. | [package and setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#package--entry-points), [components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking), [runtime quirks](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks) |
+| Caching and request deduplication | Advanced or production-only | Prevent profile-specific output from entering shared caches without recreating SDK request plumbing. | A cache-safety table; the request family's one-initialization-per-RSC-request contract and separation across requests; the distinction between SDK initialization sharing, managed cache/dedup, the single handoff merge, and shared output caching; managed baseline fetching/prefetch overlaps request initialization automatically while selected state waits for both; no app-owned request cache, duplicate awaits, or performance option; links to the handoff concept and supplemental rendering guide; and a two-profile validation. | [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [runtime quirks](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks), [managed fetching](../../internal/sdk-knowledge/shared/concepts.md#entry-source-boundary-managed-or-manual), [handoff](../../internal/sdk-knowledge/shared/concepts.md#optimization-handoff) |
+| Strict consent and duplicate-event controls | Advanced or production-only | Turn privacy and duplicate-event policy into explicit production decisions. | A strict event-policy example, blocked-event diagnostics, an accepted local page-event check triggered by a normal participating Next.js `Link` navigation after consent, first-event ownership, and the separate steps in consent withdrawal. | [App Router consent](../../internal/sdk-knowledge/web/nextjs-app-router.md#consent--persistence), [App Router events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking), [React Web consent](../../internal/sdk-knowledge/web/react-web.md#consent--persistence), [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [Web failure behavior](../../internal/sdk-knowledge/web/web.md#failure--fallback-behavior) |
## SDK-specific authoring overrides
@@ -100,6 +100,11 @@ guide: ../../guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app.
- Keep the integration guide focused on request-family initialization and route ownership. Static,
ISR, edge, and analytics-only recipes use top-level request-free surfaces and live in the
supplemental rendering guide; deeper handoff mechanics live in the concept.
+- For private App Router requests and per-request Edge handoffs, describe server work as event
+ preparation plus per-request preflight for rendering. Browser takeover admits the prepared batch
+ through the SDK queue; preflight is not commitment. Keep private replay and API-issued profile
+ identity out of static/ISR output. Use public-permutation handoffs only for explicit
+ visitor-independent selections, or shared baseline output with browser-owned personalization.
- Keep the quick start self-contained: show where the Optimization values come from and their
`.env.local` placement, gloss the consent and hydration settings, define the public-chrome,
provider-dependent body, and meaningful-fallback responsibilities, and explain the
diff --git a/documentation/authoring/blueprints/nextjs-pages-router.md b/documentation/authoring/blueprints/nextjs-pages-router.md
index a6ba5bb7e..83b26d9fb 100644
--- a/documentation/authoring/blueprints/nextjs-pages-router.md
+++ b/documentation/authoring/blueprints/nextjs-pages-router.md
@@ -35,23 +35,23 @@ guide: ../../guides/integrating-the-optimization-sdk-in-a-nextjs-pages-router-ap
## Section map
-| Section | Category | Purpose | Must teach or show | Fact sources |
-| ------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| How the SDK fits your app | Required for first integration | Establish the explicit browser/server module split. | Import-path table; client and server binding modules; bridge from quick start. | [package and setup](../../internal/sdk-knowledge/web/nextjs-pages-router.md#package--entry-points) |
-| Fetching Contentful entries | Required for first integration | Establish manual and managed server entry sources. | Introduce the `ManagedEntryDescriptor` import, `contentful.client` on the browser/server bindings, and the wrapper's `prefetchManagedEntries` input here rather than in the manual-baseline quick start; manual path plus an actual dynamic route whose parameter builds a direct ID/slug descriptor for request prefetch and passes the object descriptor to bound `OptimizedEntry` under `managedEntry`; fixed SDK property names versus app/model values; one shared helper supplies the same effective source values to request prefetch, browser render, and handoff; `entryQuery` and default slug field; isolated exact not-found/duplicate templates with placeholder explanation; slug handoff nests the descriptor under `managedEntry` and retains fetched `sys.id` as `entryId`; locale/include/client ownership. | [rendering](../../internal/sdk-knowledge/web/nextjs-pages-router.md#render--entry-resolution), [entry source](../../internal/sdk-knowledge/shared/concepts.md#entry-source-boundary-managed-or-manual) |
-| The getServerSideProps request handoff and the profile cookie | Required for first integration | Teach how server decisions reach `_app.tsx`. | Bind the server SDK with `bindNextjsPagesRouterServerOptimization`; call its returned `createRequestHandoff` from `getServerSideProps`; complete props merge; define any app-owned wrapper before use; failure handling; cookie ownership; explicit no-middleware note. | [setup](../../internal/sdk-knowledge/web/nextjs-pages-router.md#setup--initialization-and-binding), [identifiers](../../internal/sdk-knowledge/web/nextjs-pages-router.md#identifier-ownership) |
-| The bound root and page events | Required for first integration | Apply serialized state before children and assign first-event ownership. | Keep the normal `_app.tsx` root-plus-tracker path first. Then show an adaptable client-binder `beforeInitialPage` path that captures the callback only for the bound content root; define the initial page decision at first use and show the server event -> handoff -> live browser runtime -> callback -> direct page attempt or same-route handoff skip -> attempted-route mark -> later-route sequence; define early identity, retained lifetime, after-live-owned-runtime invocation, `identify`/`screen`/`track`, Promise/thenable, direct attempt, emitter, initial `skip`, and `{ accepted: false }`; represent multiple awaited operations with one returned Promise; require `routeKey` and lazy `buildPagePayload` while rejecting eager `initialPagePayload`; remove the separate tracker; and teach watchdog/error continuation only while mounted on the current live runtime, best-effort and verified in-flight route-interleaving limits, a visible five-second fallback/freeze callout with live-update remedy, and a performable accepted/blocked ordering/duplicate-page check. Qualify production checks and troubleshooting with normal tracker mode versus `beforeInitialPage` mode, route Optional readers here without a new section, and preserve provider/analytics exclusions. State narrowly that the Pages server binder rejects a client config containing `beforeInitialPage`, does not run its callback, and does not serialize it into the handoff passed through page props. | [components](../../internal/sdk-knowledge/web/nextjs-pages-router.md#components--hooks), [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking), [React Web setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding), [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [React Web failure](../../internal/sdk-knowledge/web/react-web.md#failure--fallback-behavior) |
-| Personalizing entries | Required for first integration | Complete Milestone 1 at the renderer boundary. | Render-prop diff; one skeleton union and content-type narrowing; fallback; nested-wrap warning; managed-prefetch shape. | [rendering](../../internal/sdk-knowledge/web/nextjs-pages-router.md#render--entry-resolution) |
-| Browser takeover and live updates | Common but policy-dependent | Implement Milestone 2 when mounted content must change. | App-wide and per-entry controls; identity/reset example. | [components](../../internal/sdk-knowledge/web/nextjs-pages-router.md#components--hooks) |
-| Entry interaction tracking | Common but policy-dependent | Explain default browser interactions and consent. | Defaults; opt-outs; resolved-entry metadata. | [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking) |
-| Consent, identity, profile, and reset | Common but policy-dependent | Replace the quick-start shortcut across server and browser. | App-owned cookie example; server callback; browser actions. | [consent](../../internal/sdk-knowledge/web/nextjs-pages-router.md#consent--persistence), [identifiers](../../internal/sdk-knowledge/web/nextjs-pages-router.md#identifier-ownership) |
-| Analytics forwarding | Optional | Hand off to the supplemental workflow. | Early subscription seam; duplicate-forwarding warning; link. | [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking) |
-| Merge tags and Custom Flags | Optional | Cover content-model-dependent reads. | Guard/resolver and flag examples. | [rendering](../../internal/sdk-knowledge/web/nextjs-pages-router.md#render--entry-resolution) |
-| Preview panel | Optional | Add environment-gated authoring tooling. | Separate package; readiness attach; reader-owned gate. | [runtime](../../internal/sdk-knowledge/web/nextjs-pages-router.md#version--runtime-quirks) |
-| Mixed route strategies | Advanced or production-only | Help mature apps choose ownership by route. | Strategy decision table and links to the supplemental rendering guide for static, ISR, edge, and analytics-only recipes. | [runtime](../../internal/sdk-knowledge/web/nextjs-pages-router.md#version--runtime-quirks), [handoff](../../internal/sdk-knowledge/shared/concepts.md#optimization-handoff) |
-| Manual server and client escape hatches | Advanced or production-only | Expose lower-level APIs after the bound path. | Server/client API map and ownership boundary. | [package and setup](../../internal/sdk-knowledge/web/nextjs-pages-router.md#package--entry-points) |
-| Caching and request policy | Advanced or production-only | Keep personalized state and HTML out of shared caches. | Cache-safety table, request failure policy, and links to the handoff concept and supplemental rendering guide. | [runtime](../../internal/sdk-knowledge/web/nextjs-pages-router.md#version--runtime-quirks), [handoff](../../internal/sdk-knowledge/shared/concepts.md#optimization-handoff) |
-| Strict consent and duplicate-event controls | Advanced or production-only | Make production event posture explicit. | Empty allow-list; blocked diagnostics; first-event check. | [consent](../../internal/sdk-knowledge/web/nextjs-pages-router.md#consent--persistence), [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking) |
+| Section | Category | Purpose | Must teach or show | Fact sources |
+| ------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| How the SDK fits your app | Required for first integration | Establish the explicit browser/server module split. | Import-path table; client and server binding modules; bridge from quick start. | [package and setup](../../internal/sdk-knowledge/web/nextjs-pages-router.md#package--entry-points) |
+| Fetching Contentful entries | Required for first integration | Establish manual and managed server entry sources. | Introduce the `ManagedEntryDescriptor` import, `contentful.client` on the browser/server bindings, and the wrapper's `prefetchManagedEntries` input here rather than in the manual-baseline quick start; manual path plus an actual dynamic route whose parameter builds a direct ID/slug descriptor for request prefetch and passes the object descriptor to bound `OptimizedEntry` under `managedEntry`; fixed SDK property names versus app/model values; one shared helper supplies the same effective source values to request prefetch, browser render, and handoff; `entryQuery` and default slug field; isolated exact not-found/duplicate templates with placeholder explanation; slug handoff nests the descriptor under `managedEntry` and retains fetched `sys.id` as `entryId`; locale/include/client ownership. | [rendering](../../internal/sdk-knowledge/web/nextjs-pages-router.md#render--entry-resolution), [entry source](../../internal/sdk-knowledge/shared/concepts.md#entry-source-boundary-managed-or-manual) |
+| The getServerSideProps request handoff and the profile cookie | Required for first integration | Prepare the server render and deliver its initial events to the browser through `_app.tsx`. | Bind the server SDK with `bindNextjsPagesRouterServerOptimization`; call its returned `createRequestHandoff` from `getServerSideProps`; explain that it prepares the initial event batch and evaluates with per-request preflight, while browser commitment happens later through the Web SDK's existing Experience queue; include the complete props merge; define any app-owned wrapper before use; failure handling; API-issued profile-ID cookie ownership; explicit no-middleware note. Do not describe server preview as an accepted committed page event or a browser skip signal. | [setup](../../internal/sdk-knowledge/web/nextjs-pages-router.md#setup--initialization-and-binding), [identifiers](../../internal/sdk-knowledge/web/nextjs-pages-router.md#identifier-ownership), [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking) |
+| The bound root and page events | Required for first integration | Apply serialized state before children and assign first-event ownership. | Keep the normal `_app.tsx` root-plus-tracker path first. Explain that the server prepares the initial event batch and runs request-scoped preflight for personalized rendering; the browser tracker passes the handoff to the Web SDK, whose existing Experience queue admits the prepared batch as one submission. Queue admission is independent of HTTP delivery, and a matching replay prevents a second ordinary page submission. Keep paired `getServerSideProps` handoff and request `initialEvents` as the primary flow. Every valid, consent-eligible request handoff contains replay even when preview fails, so the bound root bypasses `beforeInitialPage`. Demonstrate the optional callback only on a browser-owned route with no handoff: use the client binder's direct `OptimizationRoot`, pass a stable `routeKey` and lazy `buildPagePayload`, and let that root own initial and later page tracking without a separate tracker. Show the callback -> direct page attempt -> attempted-route mark -> later-route sequence; define early identity, retained lifetime, after-live-owned-runtime invocation, `identify`/`screen`/`track`, Promise/thenable, direct attempt, emitter, initial `skip`, and `{ accepted: false }`; represent multiple awaited operations with one returned Promise; require `routeKey` and lazy `buildPagePayload` while rejecting eager `initialPagePayload`; remove the separate tracker; and teach watchdog/error continuation only while mounted on the current live runtime, best-effort and verified in-flight route-interleaving limits, a visible five-second fallback/freeze callout with live-update remedy, and a performable accepted/blocked ordering/duplicate-page check. Qualify production checks and troubleshooting with normal tracker mode versus `beforeInitialPage` mode, route Optional readers here without a new section, and preserve provider/analytics exclusions. State narrowly that the Pages server binder rejects a client config containing `beforeInitialPage`, does not run its callback, and does not serialize it into the handoff passed through page props. | [components](../../internal/sdk-knowledge/web/nextjs-pages-router.md#components--hooks), [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking), [React Web setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding), [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [React Web failure](../../internal/sdk-knowledge/web/react-web.md#failure--fallback-behavior) |
+| Personalizing entries | Required for first integration | Complete Milestone 1 at the renderer boundary. | Render-prop diff; one skeleton union and content-type narrowing; fallback; nested-wrap warning; managed-prefetch shape. | [rendering](../../internal/sdk-knowledge/web/nextjs-pages-router.md#render--entry-resolution) |
+| Browser takeover and live updates | Common but policy-dependent | Implement Milestone 2 when mounted content must change. | App-wide and per-entry controls; identity/reset example. | [components](../../internal/sdk-knowledge/web/nextjs-pages-router.md#components--hooks) |
+| Entry interaction tracking | Common but policy-dependent | Explain default browser interactions and consent. | Defaults; opt-outs; resolved-entry metadata. | [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking) |
+| Consent, identity, profile, and reset | Common but policy-dependent | Replace the quick-start shortcut across server and browser. | App-owned cookie example; server callback; browser actions. | [consent](../../internal/sdk-knowledge/web/nextjs-pages-router.md#consent--persistence), [identifiers](../../internal/sdk-knowledge/web/nextjs-pages-router.md#identifier-ownership) |
+| Analytics forwarding | Optional | Hand off to the supplemental workflow. | Early subscription seam; duplicate-forwarding warning; link. | [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking) |
+| Merge tags and Custom Flags | Optional | Cover content-model-dependent reads. | Guard/resolver and flag examples. | [rendering](../../internal/sdk-knowledge/web/nextjs-pages-router.md#render--entry-resolution) |
+| Preview panel | Optional | Add environment-gated authoring tooling. | Separate package; readiness attach; reader-owned gate. | [runtime](../../internal/sdk-knowledge/web/nextjs-pages-router.md#version--runtime-quirks) |
+| Mixed route strategies | Advanced or production-only | Help mature apps choose ownership by route. | Strategy decision table and links to the supplemental rendering guide for static, ISR, edge, and analytics-only recipes. | [runtime](../../internal/sdk-knowledge/web/nextjs-pages-router.md#version--runtime-quirks), [handoff](../../internal/sdk-knowledge/shared/concepts.md#optimization-handoff) |
+| Manual server and client escape hatches | Advanced or production-only | Expose lower-level APIs after the bound path. | Server/client API map and ownership boundary. | [package and setup](../../internal/sdk-knowledge/web/nextjs-pages-router.md#package--entry-points) |
+| Caching and request policy | Advanced or production-only | Keep personalized state and HTML out of shared caches. | Cache-safety table, request failure policy, and links to the handoff concept and supplemental rendering guide. | [runtime](../../internal/sdk-knowledge/web/nextjs-pages-router.md#version--runtime-quirks), [handoff](../../internal/sdk-knowledge/shared/concepts.md#optimization-handoff) |
+| Strict consent and duplicate-event controls | Advanced or production-only | Make production event posture explicit. | Empty allow-list; blocked diagnostics; first-event check. | [consent](../../internal/sdk-knowledge/web/nextjs-pages-router.md#consent--persistence), [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking) |
## SDK-specific authoring overrides
@@ -62,6 +62,11 @@ guide: ../../guides/integrating-the-optimization-sdk-in-a-nextjs-pages-router-ap
- Keep the integration guide focused on Pages request handoff. Static, ISR, edge, and analytics-only
selection handoff recipes live in the supplemental rendering guide; deeper handoff mechanics live
in the concept.
+- Explain that `getServerSideProps` and per-request Edge integrations prepare and preflight their
+ initial event batch for rendering; the browser commits the batch through the SDK queue. Keep
+ private replay and API-issued profile identity out of `getStaticProps`/ISR output. Static routes
+ use public-permutation handoffs only for explicit visitor-independent selections, or shared
+ baseline output with browser-owned personalization.
## Troubleshooting scope
diff --git a/documentation/authoring/blueprints/node.md b/documentation/authoring/blueprints/node.md
index 42e7c006e..5c85ff77b 100644
--- a/documentation/authoring/blueprints/node.md
+++ b/documentation/authoring/blueprints/node.md
@@ -51,6 +51,10 @@ guide: ../../guides/integrating-the-node-sdk-in-a-node-app.md
## SDK-specific authoring overrides
- Use a server `process.env` note rather than the recipe's browser-visible environment convention.
+- When explaining Node/Web continuity, distinguish ordinary Node `page()` delivery from paired
+ `prepareRequestHandoff()` preparation: the latter uses per-request preflight for rendering and
+ the browser admits the prepared event batch through its existing queue. Profile identity is
+ supplied by Experience API responses.
- Do not force a React render-prop example into this guide; explain the skeleton union, content-type
narrowing, and mutation boundary instead. Facts:
[rendering](../../internal/sdk-knowledge/node/node.md#render--entry-resolution).
diff --git a/documentation/authoring/blueprints/react-web.md b/documentation/authoring/blueprints/react-web.md
index 5bdbe36d1..e617da906 100644
--- a/documentation/authoring/blueprints/react-web.md
+++ b/documentation/authoring/blueprints/react-web.md
@@ -32,23 +32,23 @@ guide: ../../guides/integrating-the-react-web-sdk-in-a-react-app.md
## Section map
-| Section | Category | Purpose | Must teach or show | Fact sources |
-| ---------------------------------------------- | ------------------------------ | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| How the SDK fits your app | Required for first integration | Establish the single root and provider composition. | Root diff; config ownership; one-mount rule. | [setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding) |
-| SDK readiness, loading, and error states | Required for first integration | Make the asynchronous browser lifecycle explicit. | Readiness timeline; loading/error UI; baseline-reveal behavior. | [runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks), [fallback](../../internal/sdk-knowledge/web/react-web.md#failure--fallback-behavior) |
-| Fetching Contentful entries | Required for first integration | Explain manual and managed entry sources. | Manual `baselineEntry`; managed `entryId` plus `entryQuery`; object descriptors under `managedEntry={{ contentType, slug, slugField?, entryQuery? }}` for components and hooks; default slug field; refetch behavior; exact not-found/duplicate errors; real `sys.id`; client/locale/include ownership. | [rendering](../../internal/sdk-knowledge/web/react-web.md#render--entry-resolution), [entry source](../../internal/sdk-knowledge/shared/concepts.md#entry-source-boundary-managed-or-manual) |
-| Resolving entries and rendering the result | Required for first integration | Complete Milestone 1 at the component boundary. | Render prop; one skeleton union and narrowing; host/double-wrap rules; fallback. | [rendering](../../internal/sdk-knowledge/web/react-web.md#render--entry-resolution) |
-| Page events and route tracking | Required for first integration | Keep route-based evaluation current. | Router tracker example; first-route behavior; one-tracker rule. | [events](../../internal/sdk-knowledge/web/react-web.md#events--tracking) |
-| Consent and privacy handoff | Common but policy-dependent | Replace the quick-start shortcut. | Two-axis consent example; pre-consent admission; app-owned record. | [consent](../../internal/sdk-knowledge/web/react-web.md#consent--persistence) |
-| Entry interaction tracking | Common but policy-dependent | Explain component-owned view/click/hover instrumentation. | Defaults and opt-outs; consent relationship. | [events](../../internal/sdk-knowledge/web/react-web.md#events--tracking) |
-| Identity, profile, and reset | Common but policy-dependent | Integrate authentication lifecycle. | Identify/reset example and consent persistence distinction. | [consent](../../internal/sdk-knowledge/web/react-web.md#consent--persistence) |
-| Run work before the initial page decision | Optional | Sequence returned work before root-owned page tracking. | Adaptable owned-root `beforeInitialPage` alternative to the normal router tracker; define the initial page decision at first use; plain-language retained-lifetime, after-live-owned-runtime invocation, `identify`/`screen`/`track`, Promise/thenable, direct-attempt, emitter, initial-`skip`, and `{ accepted: false }` definitions; multiple awaited operations represented by one returned Promise; callback -> latest direct page attempt or same-route handoff skip -> attempted-route initial `skip` mark -> later-route `emit`; app ownership of stable `routeKey` and lazy `buildPagePayload` versus eager `initialPagePayload`; sole-page-owner rule across every later tracker example/check; watchdog domain and exact invalid-config failure; callback/page failure continuation only while mounted on the current live runtime; fire-and-forget, non-cancellation, verified in-flight route-interleaving, and default-freeze limits; visible five-second fallback/freeze callout with live-update remedy; minimal inline development-only accepted/blocked observer with cleanup and a performable ordering/duplicate-page check; injected-provider, analytics, direct Web, and direct Node boundaries. | [setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding), [events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [fallback](../../internal/sdk-knowledge/web/react-web.md#failure--fallback-behavior), [runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks) |
-| Live updates | Optional | Implement Milestone 2 without implying it is default. | Root and per-entry scopes; state-change example. | [runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks) |
-| Merge tags and Custom Flags | Optional | Cover content-model-dependent reads. | Hook/render helper examples and event behavior. | [rendering](../../internal/sdk-knowledge/web/react-web.md#render--entry-resolution) |
-| Analytics forwarding | Optional | Hand off to the supplemental workflow. | Early subscription seam and message dedupe; guide link. | [events](../../internal/sdk-knowledge/web/react-web.md#events--tracking) |
-| Preview panel | Optional | Add environment-gated authoring tooling. | Separate-package dynamic attach and readiness seam. | [runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks) |
-| Owning the Web SDK instance | Advanced or production-only | Explain explicit provider composition and SSR handoff. | Injected instance; provider stack; direct ID/slug prefetch descriptors; slug handoff nests its descriptor under `managedEntry`, retains the fetched `sys.id` as `entryId`, and matches the same effective source values. | [components](../../internal/sdk-knowledge/web/react-web.md#components--hooks), [entry source](../../internal/sdk-knowledge/shared/concepts.md#entry-source-boundary-managed-or-manual), [runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks) |
-| Strict consent, storage, and delivery controls | Advanced or production-only | Expose production posture. | Empty allow-list; storage/queue settings; blocked diagnostics. | [consent](../../internal/sdk-knowledge/web/react-web.md#consent--persistence) |
+| Section | Category | Purpose | Must teach or show | Fact sources |
+| ---------------------------------------------- | ------------------------------ | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| How the SDK fits your app | Required for first integration | Establish the single root and provider composition. | Root diff; config ownership; one-mount rule. | [setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding) |
+| SDK readiness, loading, and error states | Required for first integration | Make the asynchronous browser lifecycle explicit. | Readiness timeline; loading/error UI; baseline-reveal behavior. | [runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks), [fallback](../../internal/sdk-knowledge/web/react-web.md#failure--fallback-behavior) |
+| Fetching Contentful entries | Required for first integration | Explain manual and managed entry sources. | Manual `baselineEntry`; managed `entryId` plus `entryQuery`; object descriptors under `managedEntry={{ contentType, slug, slugField?, entryQuery? }}` for components and hooks; default slug field; refetch behavior; exact not-found/duplicate errors; real `sys.id`; client/locale/include ownership. | [rendering](../../internal/sdk-knowledge/web/react-web.md#render--entry-resolution), [entry source](../../internal/sdk-knowledge/shared/concepts.md#entry-source-boundary-managed-or-manual) |
+| Resolving entries and rendering the result | Required for first integration | Complete Milestone 1 at the component boundary. | Render prop; one skeleton union and narrowing; host/double-wrap rules; fallback. | [rendering](../../internal/sdk-knowledge/web/react-web.md#render--entry-resolution) |
+| Page events and route tracking | Required for first integration | Keep route-based evaluation current. | Router tracker example; first-route behavior; one-tracker rule. | [events](../../internal/sdk-knowledge/web/react-web.md#events--tracking) |
+| Consent and privacy handoff | Common but policy-dependent | Replace the quick-start shortcut. | Two-axis consent example; pre-consent admission; app-owned record. | [consent](../../internal/sdk-knowledge/web/react-web.md#consent--persistence) |
+| Entry interaction tracking | Common but policy-dependent | Explain component-owned view/click/hover instrumentation. | Defaults and opt-outs; consent relationship. | [events](../../internal/sdk-knowledge/web/react-web.md#events--tracking) |
+| Identity, profile, and reset | Common but policy-dependent | Integrate authentication lifecycle. | Identify/reset example and consent persistence distinction. | [consent](../../internal/sdk-knowledge/web/react-web.md#consent--persistence) |
+| Run work before the initial page decision | Optional | Sequence returned work before root-owned page tracking. | Adaptable owned-root `beforeInitialPage` alternative to the normal router tracker; define the initial page decision at first use; plain-language retained-lifetime, after-live-owned-runtime invocation, `identify`/`screen`/`track`, Promise/thenable, direct-attempt, emitter, initial-`skip`, and `{ accepted: false }` definitions; multiple awaited operations represented by one returned Promise; a prepared replay bypasses the callback and follows the normal handoff queue path; without replay, callback -> latest direct page attempt -> attempted-route initial `skip` mark -> later-route `emit`; app ownership of stable `routeKey` and lazy `buildPagePayload` versus eager `initialPagePayload`; sole-page-owner rule across every later tracker example/check; watchdog domain and exact invalid-config failure; callback/page failure continuation only while mounted on the current live runtime; fire-and-forget, non-cancellation, verified in-flight route-interleaving, and default-freeze limits; visible five-second fallback/freeze callout with live-update remedy; minimal inline development-only accepted/blocked observer with cleanup and a performable ordering/duplicate-page check; injected-provider, analytics, direct Web, and direct Node boundaries. | [setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding), [events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [fallback](../../internal/sdk-knowledge/web/react-web.md#failure--fallback-behavior), [runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks) |
+| Live updates | Optional | Implement Milestone 2 without implying it is default. | Root and per-entry scopes; state-change example. | [runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks) |
+| Merge tags and Custom Flags | Optional | Cover content-model-dependent reads. | Hook/render helper examples and event behavior. | [rendering](../../internal/sdk-knowledge/web/react-web.md#render--entry-resolution) |
+| Analytics forwarding | Optional | Hand off to the supplemental workflow. | Early subscription seam and message dedupe; guide link. | [events](../../internal/sdk-knowledge/web/react-web.md#events--tracking) |
+| Preview panel | Optional | Add environment-gated authoring tooling. | Separate-package dynamic attach and readiness seam. | [runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks) |
+| Owning the Web SDK instance | Advanced or production-only | Explain explicit provider composition and SSR handoff. | Injected instance; provider stack; direct ID/slug prefetch descriptors; slug handoff nests its descriptor under `managedEntry`, retains the fetched `sys.id` as `entryId`, and matches the same effective source values. | [components](../../internal/sdk-knowledge/web/react-web.md#components--hooks), [entry source](../../internal/sdk-knowledge/shared/concepts.md#entry-source-boundary-managed-or-manual), [runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks) |
+| Strict consent, storage, and delivery controls | Advanced or production-only | Expose production posture. | Empty allow-list; storage/queue settings; blocked diagnostics. | [consent](../../internal/sdk-knowledge/web/react-web.md#consent--persistence) |
## SDK-specific authoring overrides
diff --git a/documentation/authoring/migration-blueprints/experience-js-nextjs-app-router.md b/documentation/authoring/migration-blueprints/experience-js-nextjs-app-router.md
index bbcdfc67c..c0251f62a 100644
--- a/documentation/authoring/migration-blueprints/experience-js-nextjs-app-router.md
+++ b/documentation/authoring/migration-blueprints/experience-js-nextjs-app-router.md
@@ -30,14 +30,14 @@ guide: ../../guides/migrating-experience-js-next-to-nextjs-app-router.md
## Section plan
-| Section | Purpose | Must route to | Fact sources |
-| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Remove legacy Next.js package assumptions | Prevent writers from treating ESR middleware/selector source as supported imports. | Legacy package mapping and unsupported boundaries. | [package mapping](../../internal/migration-knowledge/experience-js.md#package-mapping), [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [unsupported boundaries](../../internal/migration-knowledge/experience-js.md#unsupported-or-manual-migration-boundaries) |
-| Install and bind the App Router SDK | Restore the explicit server binding, nested request family, and forwarding-only handler before replacing render calls. | App Router setup, package, and runtime sections. | [package](../../internal/sdk-knowledge/web/nextjs-app-router.md#package--entry-points), [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [runtime](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks) |
-| Replace SSR/ESR profile continuity | Move profile cookie and request evaluation to the SDK-owned request family while consent remains app-owned. | Request context, identifiers, consent, and first-event ownership. | [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [legacy identifiers](../../internal/migration-knowledge/experience-js.md#identifiers-and-persistence), [target identifiers](../../internal/sdk-knowledge/web/nextjs-app-router.md#identifier-ownership), [events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking) |
-| Replace server-rendered personalization | Route legacy mapped experiences and React wrappers to target entry resolution and server first paint. | App Router rendering and content-model migration. | [React legacy](../../internal/migration-knowledge/experience-js.md#react-render-and-hooks), [content model](../../internal/migration-knowledge/experience-js.md#contentful-model-and-mapper), [rendering](../../internal/sdk-knowledge/web/nextjs-app-router.md#render--entry-resolution), [entry resolution](../../internal/sdk-knowledge/shared/concepts.md#entry-resolution) |
-| Replace browser takeover features | Move flags, analytics forwarding, preview, and live updates through the target client runtime. | App Router browser/client sections and supplemental plugin guide. | [plugins](../../internal/migration-knowledge/experience-js.md#plugins-and-preview), [flag views](../../internal/sdk-knowledge/shared/concepts.md#custom-flag-views), [event streams](../../internal/sdk-knowledge/shared/concepts.md#stateful-event-forwarding-streams), [preview](../../internal/sdk-knowledge/shared/concepts.md#preview-overrides), [live updates](../../internal/sdk-knowledge/shared/concepts.md#live-updates) |
-| Validate App Router migration | Verify server HTML, hydration stability, profile continuity, event ownership, and cache safety. | App Router production checks and troubleshooting. | [runtime](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks), [fallback](../../internal/sdk-knowledge/web/nextjs-app-router.md#failure--fallback-behavior), [events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking) |
+| Section | Purpose | Must route to | Fact sources |
+| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Remove legacy Next.js package assumptions | Prevent writers from treating ESR middleware/selector source as supported imports. | Legacy package mapping and unsupported boundaries. | [package mapping](../../internal/migration-knowledge/experience-js.md#package-mapping), [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [unsupported boundaries](../../internal/migration-knowledge/experience-js.md#unsupported-or-manual-migration-boundaries) |
+| Install and bind the App Router SDK | Restore the explicit server binding, nested request family, and forwarding-only handler before replacing render calls. | App Router setup, package, and runtime sections. | [package](../../internal/sdk-knowledge/web/nextjs-app-router.md#package--entry-points), [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [runtime](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks) |
+| Replace SSR/ESR profile continuity | Move profile cookie and request preparation to the SDK-owned request family while consent remains app-owned. The server uses per-request preflight for personalized rendering; browser takeover commits the prepared batch through the SDK queue. Preserve API-issued profile identity and do not treat preview as delivery. | Request context, identifiers, consent, and browser commitment. | [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [legacy identifiers](../../internal/migration-knowledge/experience-js.md#identifiers-and-persistence), [target identifiers](../../internal/sdk-knowledge/web/nextjs-app-router.md#identifier-ownership), [events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking) |
+| Replace server-rendered personalization | Route legacy mapped experiences and React wrappers to target entry resolution and server first paint. | App Router rendering and content-model migration. | [React legacy](../../internal/migration-knowledge/experience-js.md#react-render-and-hooks), [content model](../../internal/migration-knowledge/experience-js.md#contentful-model-and-mapper), [rendering](../../internal/sdk-knowledge/web/nextjs-app-router.md#render--entry-resolution), [entry resolution](../../internal/sdk-knowledge/shared/concepts.md#entry-resolution) |
+| Replace browser takeover features | Move flags, analytics forwarding, preview, and live updates through the target client runtime. | App Router browser/client sections and supplemental plugin guide. | [plugins](../../internal/migration-knowledge/experience-js.md#plugins-and-preview), [flag views](../../internal/sdk-knowledge/shared/concepts.md#custom-flag-views), [event streams](../../internal/sdk-knowledge/shared/concepts.md#stateful-event-forwarding-streams), [preview](../../internal/sdk-knowledge/shared/concepts.md#preview-overrides), [live updates](../../internal/sdk-knowledge/shared/concepts.md#live-updates) |
+| Validate App Router migration | Verify server HTML, hydration stability, profile continuity, event ownership, and cache safety. | App Router production checks and troubleshooting. | [runtime](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks), [fallback](../../internal/sdk-knowledge/web/nextjs-app-router.md#failure--fallback-behavior), [events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking) |
## Handoffs
diff --git a/documentation/authoring/migration-blueprints/experience-js-nextjs-pages-router.md b/documentation/authoring/migration-blueprints/experience-js-nextjs-pages-router.md
index 4fe4f9224..b1a4bd5d0 100644
--- a/documentation/authoring/migration-blueprints/experience-js-nextjs-pages-router.md
+++ b/documentation/authoring/migration-blueprints/experience-js-nextjs-pages-router.md
@@ -15,8 +15,9 @@ guide: ../../guides/migrating-experience-js-next-to-nextjs-pages-router.md
page tracking, and target entry resolution.
- **Guide file:** `documentation/guides/migrating-experience-js-next-to-nextjs-pages-router.md`
- **Write after:** `choosing-a-nextjs-migration-path-from-experience-js.md`.
-- **First verification:** One Pages Router server data path renders an all-visitors variant and
- verifies accepted server evaluation plus denied-consent behavior.
+- **First verification:** One Pages Router server data path renders an all-visitors variant; a
+ browser observer confirms the prepared page batch is admitted when consent permits, and denied
+ consent blocks admission. Treat server preflight as a rendering preview, not committed delivery.
## Migration route
@@ -29,14 +30,14 @@ guide: ../../guides/migrating-experience-js-next-to-nextjs-pages-router.md
## Section plan
-| Section | Purpose | Must route to | Fact sources |
-| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Inventory legacy Pages Router wiring | Identify provider placement, tracker override, SSR plugin use, and profile cookie assumptions. | Legacy Next.js and identifiers. | [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [identifiers](../../internal/migration-knowledge/experience-js.md#identifiers-and-persistence) |
-| Install and bind the Pages Router SDK | Establish the target package, browser binding, and `bindNextjsPagesRouterServerOptimization` before rendering changes. | Pages setup and runtime sections. | [package](../../internal/sdk-knowledge/web/nextjs-pages-router.md#package--entry-points), [setup](../../internal/sdk-knowledge/web/nextjs-pages-router.md#setup--initialization-and-binding), [runtime](../../internal/sdk-knowledge/web/nextjs-pages-router.md#version--runtime-quirks) |
-| Replace server profile and page evaluation | Replace the legacy SSR plugin with the returned `createRequestHandoff` call inside `getServerSideProps`; define any app-owned wrapper before use and preserve duplicate-event controls. | Pages events, identifiers, and consent. | [legacy SSR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [target identifiers](../../internal/sdk-knowledge/web/nextjs-pages-router.md#identifier-ownership), [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking), [page events](../../internal/sdk-knowledge/shared/concepts.md#page-events) |
-| Replace personalized rendering | Move legacy React components and Contentful mapper output to Pages Router entry resolution. | Pages rendering and content-model migration. | [React legacy](../../internal/migration-knowledge/experience-js.md#react-render-and-hooks), [content model](../../internal/migration-knowledge/experience-js.md#contentful-model-and-mapper), [rendering](../../internal/sdk-knowledge/web/nextjs-pages-router.md#render--entry-resolution), [entry resolution](../../internal/sdk-knowledge/shared/concepts.md#entry-resolution) |
-| Replace client-side extras | Route flags, analytics forwarding, preview, and consent diagnostics through React Web client behavior. | Pages/React Web events plus supplemental plugin guide. | [plugins](../../internal/migration-knowledge/experience-js.md#plugins-and-preview), [flag views](../../internal/sdk-knowledge/shared/concepts.md#custom-flag-views), [event streams](../../internal/sdk-knowledge/shared/concepts.md#stateful-event-forwarding-streams), [preview](../../internal/sdk-knowledge/shared/concepts.md#preview-overrides) |
-| Validate Pages Router migration | Verify server props, rendered variant/baseline, tracker ownership, profile continuity, and cache boundaries. | Pages production checks and troubleshooting. | [runtime](../../internal/sdk-knowledge/web/nextjs-pages-router.md#version--runtime-quirks), [fallback](../../internal/sdk-knowledge/web/nextjs-pages-router.md#failure--fallback-behavior), [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking) |
+| Section | Purpose | Must route to | Fact sources |
+| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Inventory legacy Pages Router wiring | Identify provider placement, tracker override, SSR plugin use, and profile cookie assumptions. | Legacy Next.js and identifiers. | [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [identifiers](../../internal/migration-knowledge/experience-js.md#identifiers-and-persistence) |
+| Install and bind the Pages Router SDK | Establish the target package, browser binding, and `bindNextjsPagesRouterServerOptimization` before rendering changes. | Pages setup and runtime sections. | [package](../../internal/sdk-knowledge/web/nextjs-pages-router.md#package--entry-points), [setup](../../internal/sdk-knowledge/web/nextjs-pages-router.md#setup--initialization-and-binding), [runtime](../../internal/sdk-knowledge/web/nextjs-pages-router.md#version--runtime-quirks) |
+| Replace server profile and page evaluation | Replace the legacy SSR plugin with the returned `createRequestHandoff` call inside `getServerSideProps`; the server prepares initial Experience events and uses per-request preflight for rendering, while the browser commits the prepared batch through the SDK queue. Preserve API-issued profile identity, define any app-owned wrapper before use, and remove legacy duplicate-event controls based on server commit/skip. | Pages events, identifiers, and consent. | [legacy SSR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [target identifiers](../../internal/sdk-knowledge/web/nextjs-pages-router.md#identifier-ownership), [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking), [page events](../../internal/sdk-knowledge/shared/concepts.md#page-events) |
+| Replace personalized rendering | Move legacy React components and Contentful mapper output to Pages Router entry resolution. | Pages rendering and content-model migration. | [React legacy](../../internal/migration-knowledge/experience-js.md#react-render-and-hooks), [content model](../../internal/migration-knowledge/experience-js.md#contentful-model-and-mapper), [rendering](../../internal/sdk-knowledge/web/nextjs-pages-router.md#render--entry-resolution), [entry resolution](../../internal/sdk-knowledge/shared/concepts.md#entry-resolution) |
+| Replace client-side extras | Route flags, analytics forwarding, preview, and consent diagnostics through React Web client behavior. | Pages/React Web events plus supplemental plugin guide. | [plugins](../../internal/migration-knowledge/experience-js.md#plugins-and-preview), [flag views](../../internal/sdk-knowledge/shared/concepts.md#custom-flag-views), [event streams](../../internal/sdk-knowledge/shared/concepts.md#stateful-event-forwarding-streams), [preview](../../internal/sdk-knowledge/shared/concepts.md#preview-overrides) |
+| Validate Pages Router migration | Verify server props, rendered variant/baseline, tracker ownership, profile continuity, and cache boundaries. | Pages production checks and troubleshooting. | [runtime](../../internal/sdk-knowledge/web/nextjs-pages-router.md#version--runtime-quirks), [fallback](../../internal/sdk-knowledge/web/nextjs-pages-router.md#failure--fallback-behavior), [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking) |
## Handoffs
diff --git a/documentation/authoring/migration-blueprints/experience-js-node-ssr-esr.md b/documentation/authoring/migration-blueprints/experience-js-node-ssr-esr.md
index 14d56f02d..3e3f89310 100644
--- a/documentation/authoring/migration-blueprints/experience-js-node-ssr-esr.md
+++ b/documentation/authoring/migration-blueprints/experience-js-node-ssr-esr.md
@@ -21,22 +21,22 @@ guide: ../../guides/migrating-experience-js-node-ssr-and-esr.md
## Migration route
-| Legacy surface | Target route | Detail owner |
-| ------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------ |
-| `NinetailedAPIClient` | Node SDK request client and event calls | [Node blueprint](../blueprints/node.md) |
-| SSR plugin profile handoff | App-owned cookie/profile continuity | [Node identifiers](../../internal/sdk-knowledge/node/node.md#identifier-ownership) |
-| ESR helper preflight behavior | App Router SDK where possible; Node/Web hybrid otherwise | Next.js router-choice migration |
-| Server-rendered content mapping | Node entry resolution or framework SDK rendering | [Node rendering](../../internal/sdk-knowledge/node/node.md#render--entry-resolution) |
+| Legacy surface | Target route | Detail owner |
+| ------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
+| `NinetailedAPIClient` | Node SDK request client and event calls | [Node blueprint](../blueprints/node.md) |
+| SSR plugin profile handoff | App-owned cookie/profile continuity | [Node identifiers](../../internal/sdk-knowledge/node/node.md#identifier-ownership) |
+| ESR helper preflight behavior | Per-request Next.js/Edge preparation and browser queue commitment | Next.js router-choice migration |
+| Server-rendered content mapping | Node entry resolution or framework SDK rendering | [Node rendering](../../internal/sdk-knowledge/node/node.md#render--entry-resolution) |
## Section plan
-| Section | Purpose | Must route to | Fact sources |
-| -------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| Inventory server profile and event ownership | Identify which server path creates profiles, emits page events, and persists visitor identity. | Legacy Node, SSR, ESR, and identifiers. | [package mapping](../../internal/migration-knowledge/experience-js.md#package-mapping), [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [identifiers](../../internal/migration-knowledge/experience-js.md#identifiers-and-persistence) |
-| Replace Node API client calls | Move server mutations and event handling to request-scoped Node SDK calls. | Node setup, context, events, and consent sections. | [legacy runtime](../../internal/migration-knowledge/experience-js.md#legacy-runtime-surfaces), [Node setup](../../internal/sdk-knowledge/node/node.md#setup--initialization-and-binding), [Node events](../../internal/sdk-knowledge/node/node.md#events--tracking), [Node consent](../../internal/sdk-knowledge/node/node.md#consent--persistence) |
-| Replace SSR and ESR handoff | Pick framework SDK when available and keep manual hybrid as an escape hatch, not the default. | Next.js router-choice guide; Node/Web continuity sections. | [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [unsupported boundaries](../../internal/migration-knowledge/experience-js.md#unsupported-or-manual-migration-boundaries), [Node identifiers](../../internal/sdk-knowledge/node/node.md#identifier-ownership), [Web identifiers](../../internal/sdk-knowledge/web/web.md#identifier-ownership) |
-| Replace server content resolution | Move from legacy experience-config mapping to target entry resolution and fallback behavior. | Node rendering and content-model migration guide. | [content model](../../internal/migration-knowledge/experience-js.md#contentful-model-and-mapper), [unsupported boundaries](../../internal/migration-knowledge/experience-js.md#unsupported-or-manual-migration-boundaries), [Node rendering](../../internal/sdk-knowledge/node/node.md#render--entry-resolution), [entry resolution](../../internal/sdk-knowledge/shared/concepts.md#entry-resolution) |
-| Validate server migration | Verify accepted or blocked events, profile continuity, rendered content, and cache safety. | Node production checks and troubleshooting. | [Node events](../../internal/sdk-knowledge/node/node.md#events--tracking), [Node fallback](../../internal/sdk-knowledge/node/node.md#failure--fallback-behavior), [Node runtime](../../internal/sdk-knowledge/node/node.md#version--runtime-quirks) |
+| Section | Purpose | Must route to | Fact sources |
+| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| Inventory server profile and event ownership | Identify which server path prepares or commits page events and persists API-issued visitor identity. Distinguish standalone Node delivery from paired request preview followed by browser queue commitment. | Legacy Node, SSR, ESR, and identifiers. | [package mapping](../../internal/migration-knowledge/experience-js.md#package-mapping), [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [identifiers](../../internal/migration-knowledge/experience-js.md#identifiers-and-persistence) |
+| Replace Node API client calls | Move server mutations and event handling to request-scoped Node SDK calls. | Node setup, context, events, and consent sections. | [legacy runtime](../../internal/migration-knowledge/experience-js.md#legacy-runtime-surfaces), [Node setup](../../internal/sdk-knowledge/node/node.md#setup--initialization-and-binding), [Node events](../../internal/sdk-knowledge/node/node.md#events--tracking), [Node consent](../../internal/sdk-knowledge/node/node.md#consent--persistence) |
+| Replace SSR and ESR handoff | Pick framework SDK when available and keep manual hybrid as an escape hatch, not the default. | Next.js router-choice guide; Node/Web continuity sections. | [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [unsupported boundaries](../../internal/migration-knowledge/experience-js.md#unsupported-or-manual-migration-boundaries), [Node identifiers](../../internal/sdk-knowledge/node/node.md#identifier-ownership), [Web identifiers](../../internal/sdk-knowledge/web/web.md#identifier-ownership) |
+| Replace server content resolution | Move from legacy experience-config mapping to target entry resolution and fallback behavior. | Node rendering and content-model migration guide. | [content model](../../internal/migration-knowledge/experience-js.md#contentful-model-and-mapper), [unsupported boundaries](../../internal/migration-knowledge/experience-js.md#unsupported-or-manual-migration-boundaries), [Node rendering](../../internal/sdk-knowledge/node/node.md#render--entry-resolution), [entry resolution](../../internal/sdk-knowledge/shared/concepts.md#entry-resolution) |
+| Validate server migration | Verify accepted or blocked events, profile continuity, rendered content, and cache safety. | Node production checks and troubleshooting. | [Node events](../../internal/sdk-knowledge/node/node.md#events--tracking), [Node fallback](../../internal/sdk-knowledge/node/node.md#failure--fallback-behavior), [Node runtime](../../internal/sdk-knowledge/node/node.md#version--runtime-quirks) |
## Handoffs
diff --git a/documentation/authoring/recipes/integration.md b/documentation/authoring/recipes/integration.md
index 51db4bd61..2817f8c33 100644
--- a/documentation/authoring/recipes/integration.md
+++ b/documentation/authoring/recipes/integration.md
@@ -26,6 +26,23 @@ Three artifacts own three different things — keep them separate:
compose time; the blueprint makes the required coverage explicit, and the source verifier checks
the claims the guide makes.
+For a paired server/browser integration, distinguish server preview from event commitment. The
+server prepares the initial Experience events and uses the per-request preflight option when it
+evaluates the request for personalized rendering. The browser receives the prepared handoff and
+admits the complete event batch through the SDK's existing queue; queue admission is separate from
+the eventual HTTP response. Do not model paired delivery as a server commit followed by a browser
+skip: the browser submits the prepared sequence, including its page event, as one batch. A matching
+replay admitted by the queue suppresses a separate ordinary-page submission. Profile IDs come from Experience API
+responses; never describe an SDK-generated profile ID.
+Give the reader a replay-specific verification step, such as a distinctive prepared event next to
+the page in the same browser batch. A page-only browser request cannot distinguish prepared replay
+from ordinary page tracking after a missing or mismatched handoff.
+For a cross-runtime recipe, show the prepared input shape, the returned render data and handoff,
+the transfer into browser code, and the browser continuation together, or link the exact guide
+section that does. When optional page owners differ, qualify later consent, validation, and
+troubleshooting steps by mode. Distinguish a recoverable preview result from an error thrown by
+application fetching or rendering code.
+
Shared-copy files contain small pieces of reader-facing wording reused verbatim. Any variation choice
is explicit in the blueprint; shared copy must not infer an SDK family or restate behavior.
diff --git a/documentation/concepts/interaction-tracking-in-node-and-stateless-environments.md b/documentation/concepts/interaction-tracking-in-node-and-stateless-environments.md
index e55656f49..da8673e71 100644
--- a/documentation/concepts/interaction-tracking-in-node-and-stateless-environments.md
+++ b/documentation/concepts/interaction-tracking-in-node-and-stateless-environments.md
@@ -432,11 +432,12 @@ delivery. Choose one of these patterns before enabling interaction tracking:
call the Node SDK with the request's profile ID. This is a manual tracking architecture, not the
Web SDK auto-tracking path.
-In Next.js server-rendered integrations, `initialPageEvent="skip"` intentionally avoids the initial
-browser Experience API `page()` request when the server or edge helper already accepted that page
-event. If that skip leaves the browser with neither a bound root or provider `handoff` nor manual
-handoff, and without a prior persisted browser profile, automatic entry views, clicks, and hovers
-cannot deliver until a later browser Experience API call populates profile state.
+In Next.js private request handoffs, the server prepares the initial page event and previews the
+result for rendering. The browser admits the prepared event batch, including that page, to its normal
+Experience queue. Keep the request handoff with the page rendered from its preview so the matching
+browser route can commit the batch and hydrate the profile state used by automatic entry tracking.
+If preview fails before an API-issued profile ID is available, the prepared batch can still reach the
+browser and use the API's profile-creation path when admitted.
If the Web SDK must read `ctfl-opt-aid`, do not mark that cookie as `HttpOnly`. Configure `path`,
`domain`, and `SameSite` so the server route and browser code refer to the same profile.
diff --git a/documentation/concepts/interaction-tracking-in-web-sdks.md b/documentation/concepts/interaction-tracking-in-web-sdks.md
index 9937588ca..fc49907aa 100644
--- a/documentation/concepts/interaction-tracking-in-web-sdks.md
+++ b/documentation/concepts/interaction-tracking-in-web-sdks.md
@@ -434,13 +434,17 @@ React Web router adapters emit `page()` calls when supported routers change rout
event helpers, not entry interaction detectors. Entry views, clicks, and hovers still come from the
Web SDK runtime.
-`OptimizationRoot` and `OptimizationAnalyticsRoot` use handoff `initialPageEvent` ownership for the
-first browser route. When an analytics-only handoff skips that route, React StrictMode effect replay
-does not emit a duplicate page event; later route-key changes still emit through the analytics
-runtime. If the analytics root unmounts or a newer hydration starts before async hydration finishes,
-the stale hydration stops before state apply, warning, or page tracking. Profileless static or
-public analytics handoffs hydrate live tracking state without overwriting durable browser
-continuity.
+`OptimizationRoot` hydrates content handoff state through the Web SDK. A matching request replay is
+admitted as one prepared Experience batch. When the handoff has no matching replay, the root emits
+an ordinary page only when supplied with `buildPagePayload` or `initialPagePayload`. Otherwise, the
+router tracker owns that fallback and uses its `pagePayload` and `getPagePayload` settings.
+
+`OptimizationAnalyticsRoot` hydrates analytics handoff state and admits a matching replay or emits
+an ordinary page through the Web SDK. Repeated calls for the current initialization share its work,
+while later visits and distinct handoffs remain trackable. If the analytics root unmounts or a newer
+hydration starts before async hydration finishes, the stale hydration stops before state apply,
+warning, or page tracking. Profileless static or public analytics handoffs hydrate live tracking
+state without overwriting durable browser continuity.
## Delivery and flushing
diff --git a/documentation/concepts/ios-sdk-runtime-and-interaction-mechanics.md b/documentation/concepts/ios-sdk-runtime-and-interaction-mechanics.md
index bb522060b..fc662d1c2 100644
--- a/documentation/concepts/ios-sdk-runtime-and-interaction-mechanics.md
+++ b/documentation/concepts/ios-sdk-runtime-and-interaction-mechanics.md
@@ -328,8 +328,8 @@ event-delivery model for SwiftUI and UIKit integrations.
Default delivery behavior:
- Experience events queue in memory when the device is offline.
-- Core caps the offline Experience queue at 100 events by default. When the queue is full, it drops
- the oldest offline events before accepting the next event.
+- Core caps the offline Experience queue at 100 events by default. When the queue is full, it rejects
+ the incoming event and retains previously accepted events in order.
- Insights events batch in memory by profile and flush periodically or when the batch reaches the
Core batch threshold.
- Queued events flush when connectivity returns. On iOS/UIKit, the SDK also performs an online
@@ -343,8 +343,9 @@ interactions while an entry session remains visible.
Use `OptimizationConfig(queuePolicy:)` when production behavior needs non-default delivery
constraints. `QueuePolicy.offlineMaxEvents` changes the Experience offline cap, `QueueFlushPolicy`
-configures retry timing, backoff, jitter, failure thresholds, and circuit-open timing, and queue
-callbacks report offline drops, flush failures, circuit-open events, and recovery. Call
+configures retry timing, backoff, jitter, failure thresholds, and circuit-open timing. The
+`onOfflineDrop` compatibility callback is deprecated and inactive; other queue callbacks report
+flush failures, circuit-open events, and recovery. Call
`client.flush()` only for deliberate app-owned checkpoints; reconnect flushing and iOS/UIKit
background flushing already perform best-effort delivery.
diff --git a/documentation/concepts/optimization-handoff-and-cache-safe-rendering.md b/documentation/concepts/optimization-handoff-and-cache-safe-rendering.md
index ef7876e13..74c10d868 100644
--- a/documentation/concepts/optimization-handoff-and-cache-safe-rendering.md
+++ b/documentation/concepts/optimization-handoff-and-cache-safe-rendering.md
@@ -28,7 +28,7 @@ from live updates, and how analytics-only markup can still carry Optimization tr
- [Cache scopes](#cache-scopes)
- [Customer-owned permutations](#customer-owned-permutations)
- [Hydration and live updates](#hydration-and-live-updates)
-- [Initial page event ownership](#initial-page-event-ownership)
+- [Server preparation and browser commitment](#server-preparation-and-browser-commitment)
- [Analytics-only handoff and tracking attributes](#analytics-only-handoff-and-tracking-attributes)
- [Why profile state stays out of public caches](#why-profile-state-stays-out-of-public-caches)
- [Related documentation](#related-documentation)
@@ -65,10 +65,14 @@ It can contain:
`sys.id` in `entryId`.
- `cache` - metadata that describes where the rendered output is allowed to be cached.
-Browser handoffs add two fields:
+Browser handoffs add one field:
- `hydration` - the browser presentation policy for already-rendered content.
-- `initialPageEvent` - whether the browser emits or skips the first page event for this route.
+
+A request handoff can also contain `replay`, an ordered batch of prepared Experience events and the
+visible route key they belong to. A matching browser route hydrates the handoff and attempts to admit
+that batch to the normal Experience queue. A route without a matching replay follows ordinary browser
+page tracking.
The SDK serializes and hydrates the state it receives. Browser hydration applies only state fields
that are present on the handoff. During Web handoff state interception, omitted interceptor fields
@@ -100,8 +104,8 @@ The handoff is not a cache key by itself. Cache safety comes from matching the r
handoff state, and the cache scope.
In App Router, managed-entry prefetch without a supplied handoff creates a baseline `static` handoff
-with `hydration: 'preserve-server'`, `selectedOptimizations: []`, and
-`initialPageEvent: 'emit'`. Treat it as baseline entry warming, not request-personalized state.
+with `hydration: 'preserve-server'` and `selectedOptimizations: []`. Treat it as baseline entry
+warming, not request-personalized state.
Prefetch accepts ID and content-type/slug descriptors. A matching browser source uses the handed-off
baseline through either the source key or resolved `sys.id`, so it does not repeat the CDA request.
@@ -193,7 +197,9 @@ becomes the browser commitment: it is the first content shown, and with live upd
place. Later selections, pending state, and failure state do not replace it or restore loading. With
live updates on, later defined selections can replace it; an empty `selectedOptimizations` array
(`[]`) means no selection applies and resolves baseline content with matching baseline tracking
-metadata. A different baseline entry ID starts a new presentation.
+metadata. A different baseline entry ID starts a new presentation. This presentation commitment is
+separate from event commitment: rendering handoff state does not mean the browser has admitted or
+delivered the prepared event batch.
`liveUpdates` controls later browser re-resolution after startup. A route can preserve the rendered
content for stable first paint and still keep live updates off. Turn live updates on when visible
@@ -206,30 +212,35 @@ existing durable browser profile continuity by suppressing durable continuity pe
handoff. A `private-request` handoff, or any profile-backed handoff that passes cache safety,
follows normal persistence behavior when persistence consent allows.
-## Initial page event ownership
-
-The first page event must have one owner.
-
-- Use `initialPageEvent: 'skip'` when a request or edge helper already accepted the first page
- event for the same route.
-- Use `initialPageEvent: 'emit'` when the browser owns the first page event for a static,
- public-permutation, or browser-owned route.
-
-Next.js request helpers set this value from the accepted page event result. The App Router request
-family passes its handoff-owned value to the nested route tracker. When the binding opts into trusted
-request handoff, a response-capable request handler can forward `pageAccepted` so the Server
-Component path does not call `page()` a second time. That forwarded context is compact: `consent`,
-`pageAccepted`, and optional `profileId`. The request family refetches profile and selection state
-server-side when `profileId` is present instead of forwarding full `OptimizationData`. Manual
-`createRequestHandoff()` remains available for advanced orchestration. Selection handoff helpers
-require application code to supply the initial page-event owner because customer-owned static and
-public permutations do not emit a server request event by themselves.
-
-React Web roots can emit the handoff-owned initial page event when they receive `routeKey` and
-either `buildPagePayload` or `initialPagePayload`. A skip can mark the initial route accepted with
-only the route key. A skip applies only to the first route hydrated from that handoff; later
-route-key changes emit browser page events. Next.js route trackers use the same `"emit"` or
-`"skip"` control for the first browser route and then track later navigations.
+## Server preparation and browser commitment
+
+For a private request, the server builds the initial `identify` and `track` events in order, followed
+by one page event. It applies server event transformations and validates the batch before previewing
+it with the Experience API. The preview uses the per-request mutation option `preflight: true`, so
+the API returns evaluated profile data for rendering without storing those event mutations. The
+handoff carries the prepared events and visible route key to the browser.
+
+On the matching browser route, the Web SDK hydrates presentation state and admits the prepared batch
+to its normal Experience queue. Admission is the browser's commitment point; the request-side
+preview is not event commitment. The queue applies current consent and handles delivery, retries,
+and response state through the same path as other Experience events. A repeated invocation for the
+current handoff joins current work instead of admitting the batch again. This guard covers the
+current initialization, not every future visit to the route. Later visits and distinct handoffs
+remain trackable.
+
+If the handoff has no replay for the current route, the browser builds and tracks an ordinary page
+event subject to current consent. If a matching prepared batch fails validation or admission, the
+Web SDK attempts ordinary current-page tracking with the supplied payload builder or its default.
+That attempt remains subject to the current route, lifecycle, deduplication, consent, and queue
+guards. An admitted replay stays in the queue for delivery and retries; an HTTP failure does not
+trigger an ordinary page attempt. Admission means the queue owns the batch; it does not guarantee
+exactly-once backend commitment when a response is lost.
+
+Static and public-permutation handoffs contain selected state and cache metadata, not request replay.
+Their browser route tracking follows the ordinary page path. App Router request components create
+the request handoff from one request-scoped preparation and pass it to the root or provider that
+renders the matching page. The framework must deliver the page's handoff across navigation;
+request memoization alone does not transfer a new handoff through a persistent layout.
## Analytics-only handoff and tracking attributes
@@ -242,11 +253,10 @@ browser SDK only for page and interaction tracking. Those routes use an analytic
- The `data-ctfl-*` attributes describe the resolved entry, baseline entry, optimization context,
variant index, sticky selection, and clickable state.
-When an analytics-only handoff skips the initial route, React StrictMode effect replay does not
-turn that skip into a duplicate browser page event. Later route-key changes still emit route events
-through the analytics runtime. If a newer analytics hydration starts or the root unmounts before
-async hydration finishes, the stale hydration stops before writing state, warning, or tracking the
-page.
+Analytics-only handoffs can also carry a prepared replay. The analytics runtime hydrates tracking
+state and admits a matching replay, or tracks the ordinary current page when no matching replay is
+present. If a newer analytics hydration starts or the root unmounts before async hydration finishes,
+the stale hydration stops before writing state, warning, or tracking the page.
Analytics-only rendering still needs the same cache decision as the markup it tracks. A static
analytics handoff is static; a public permutation needs an application-owned key; request-personalized
diff --git a/documentation/concepts/profile-synchronization-between-client-and-server.md b/documentation/concepts/profile-synchronization-between-client-and-server.md
index 1eb18e72b..4efafba80 100644
--- a/documentation/concepts/profile-synchronization-between-client-and-server.md
+++ b/documentation/concepts/profile-synchronization-between-client-and-server.md
@@ -101,9 +101,11 @@ profile-changing events:
- **Storage availability** - Browser localStorage, readable cookies, AsyncStorage, UserDefaults, or
SharedPreferences must be available for relaunch or client-server handoff continuity. Storage
failure does not change the Experience API source of truth, but it can break local durability.
-- **Preview and preflight mode** - Preview and preflight flows can evaluate profile state without
- storing normal profile mutations. Do not use them as the durability path for a profile ID you
- intend to continue.
+- **Request preview** - A private request handoff prepares its initial Experience events and uses
+ the per-request `preflight: true` mutation option to evaluate profile state for server rendering
+ without storing those mutations. The handoff carries the prepared events to the browser, where
+ the normal queue admits them for commitment. The Experience API supplies the profile ID; the SDK
+ does not create a placeholder identity when preview cannot return one.
- **Offline behavior** - Stateful clients can queue Experience events while offline, but no newer
profile data is available until a request succeeds.
- **Configured defaults** - `defaults.profile`, `defaults.selectedOptimizations`,
@@ -304,9 +306,13 @@ Consent policy belongs to the application layer on the server. A conservative se
- When consent is revoked, clear the stored profile ID and stop sending events until consent is
granted again.
-If the server uses `preflight: true`, the Experience API evaluates a profile state without storing
-the mutation. Use that for preview or evaluation flows, not as the normal continuity path for a
-profile ID you intend to persist.
+For private server-rendered handoffs, request preview is the rendering step, not the commitment
+step. The server prepares the initial event batch and previews it with the Experience API's
+per-request `preflight: true` option. The browser later admits that same prepared batch to its normal
+Experience queue. When the API returns a profile ID and persistence consent allows it, the
+application can persist that API-issued ID independently of event commitment. If preview fails
+before an ID is available, the handoff can still carry its prepared batch without an ID; the browser
+can submit it through the API's profile-creation path.
## Browser-side mechanics
@@ -510,17 +516,17 @@ cookie or session value in the same user flow.
The following cases are common sources of profile-sync bugs:
-| Case | What happens | Mitigation |
-| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `ctfl-opt-aid` is `HttpOnly` | The server can read it, but the Web SDK cannot adopt it. | Use a readable cookie for hybrid Node and Web SDK continuity. |
-| Cookie domain or path mismatch | The browser and server use different profile IDs or no shared ID. | Set `path: '/'` and a domain that covers the pages that initialize the Web SDK. |
-| Cookie differs from localStorage | The Web SDK clears cached profile-continuity data and adopts the cookie ID when persistence consent is `true`. | Treat this as expected when the server changes identity. |
-| Cookie changes after SDK construction | The running Web SDK does not continuously watch cookies. | Reinitialize intentionally after teardown or update identity through SDK event flows. |
-| Multiple browser tabs | Tabs share storage, but in-memory signals are per runtime and do not auto-sync from storage events. | Let each tab refresh state through Experience events or reload-sensitive application flows. |
-| Offline browser Experience events | Events queue locally and no new profile data is available until a successful flush. | Design UI so cached selections are acceptable while offline. |
-| Missing browser profile for Insights | Insights delivery is skipped because stateful Insights events use the current profile signal. | Ensure an Experience call has returned a profile before relying on Insights-only tracking. For direct Web SDK initialization, bootstrap a valid `defaults.profile` when the server already evaluated the profile. For private App Router rendering, use the request root or provider so the SDK supplies the request handoff. For top-level App Router selection or manual paths, pass the explicit handoff. For Pages Router, pass `pageProps.contentfulOptimization.handoff` through the bound root or provider. For React Web and manual Next.js provider or root handoff, use the `handoff` prop. |
-| Server uses `preflight` for normal flows | The API evaluates without storing the mutation, which breaks durable profile continuity expectations. | Reserve `preflight` for preview or non-persistent evaluation. |
-| Full profile serialized unnecessarily | More profile data reaches the browser than the UI needs. | Share only the profile ID unless hydration needs profile data, changes, or selections. |
+| Case | What happens | Mitigation |
+| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `ctfl-opt-aid` is `HttpOnly` | The server can read it, but the Web SDK cannot adopt it. | Use a readable cookie for hybrid Node and Web SDK continuity. |
+| Cookie domain or path mismatch | The browser and server use different profile IDs or no shared ID. | Set `path: '/'` and a domain that covers the pages that initialize the Web SDK. |
+| Cookie differs from localStorage | The Web SDK clears cached profile-continuity data and adopts the cookie ID when persistence consent is `true`. | Treat this as expected when the server changes identity. |
+| Cookie changes after SDK construction | The running Web SDK does not continuously watch cookies. | Reinitialize intentionally after teardown or update identity through SDK event flows. |
+| Multiple browser tabs | Tabs share storage, but in-memory signals are per runtime and do not auto-sync from storage events. | Let each tab refresh state through Experience events or reload-sensitive application flows. |
+| Offline browser Experience events | Events queue locally and no new profile data is available until a successful flush. | Design UI so cached selections are acceptable while offline. |
+| Missing browser profile for Insights | Insights delivery is skipped because stateful Insights events use the current profile signal. | Ensure an Experience call has returned a profile before relying on Insights-only tracking. For direct Web SDK initialization, bootstrap a valid `defaults.profile` when the server already evaluated the profile. For private App Router rendering, use the request root or provider so the SDK supplies the request handoff. For top-level App Router selection or manual paths, pass the explicit handoff. For Pages Router, pass `pageProps.contentfulOptimization.handoff` through the bound root or provider. For React Web and manual Next.js provider or root handoff, use the `handoff` prop. |
+| Server preview succeeds but browser commitment is pending | The rendered profile data comes from preview; the prepared events remain uncommitted until the browser admits them. | Keep the prepared request handoff with the matching rendered page and let the browser's normal Experience queue own admission and delivery. |
+| Full profile serialized unnecessarily | More profile data reaches the browser than the UI needs. | Share only the profile ID unless hydration needs profile data, changes, or selections. |
## Implementation checklist
diff --git a/documentation/concepts/react-native-sdk-interaction-tracking-mechanics.md b/documentation/concepts/react-native-sdk-interaction-tracking-mechanics.md
index 3bd0a50d8..74269004b 100644
--- a/documentation/concepts/react-native-sdk-interaction-tracking-mechanics.md
+++ b/documentation/concepts/react-native-sdk-interaction-tracking-mechanics.md
@@ -207,9 +207,10 @@ The React Native SDK layers React Native-specific behavior on top:
session resets without an event. Foregrounding re-evaluates visibility and starts a new session
when the entry still meets the visibility threshold.
-`queuePolicy.offlineMaxEvents` caps offline Experience events, and `queuePolicy.onOfflineDrop` is
-called when older offline Experience events are dropped to honor that cap. Insights events share the
-flush policy, but these two offline drop controls belong to the Experience queue. See the
+`queuePolicy.offlineMaxEvents` caps offline Experience events at 100 by default. Once full, the
+queue rejects an incoming event and keeps previously accepted events in order. A rejected call
+returns `{ accepted: false }`; `onOfflineDrop` is a deprecated compatibility option and is not
+called. Insights events share the flush policy, but the offline cap belongs to the Experience queue. See the
[React Native SDK README](../../packages/react-native-sdk/README.md#common-configuration) for the
common queue configuration entry point.
@@ -515,8 +516,8 @@ for exhaustive prop and type details.
even when `trackClick` is not allowed.
- The preview panel forces live entry updates while it is open so authoring overrides can render
immediately.
-- `queuePolicy.flush` controls shared retry, backoff, and circuit behavior. `offlineMaxEvents` and
- `onOfflineDrop` apply to the offline Experience buffer.
+- `queuePolicy.flush` controls shared retry, backoff, and circuit behavior. `offlineMaxEvents` caps
+ the offline Experience buffer; `onOfflineDrop` is deprecated and inactive.
Use `onStatesReady` when diagnostics or app-level observers must attach as soon as SDK state exists
and before provider children can emit `screen`, `eventStream`, or `blockedEventStream` updates.
diff --git a/documentation/guides/choosing-a-nextjs-migration-path-from-experience-js.md b/documentation/guides/choosing-a-nextjs-migration-path-from-experience-js.md
index fff0da647..263102bf3 100644
--- a/documentation/guides/choosing-a-nextjs-migration-path-from-experience-js.md
+++ b/documentation/guides/choosing-a-nextjs-migration-path-from-experience-js.md
@@ -52,7 +52,7 @@ Use these terms consistently:
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.
+3. Plan server preview and browser commitment for 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.
@@ -89,27 +89,28 @@ 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.
+and browser page tracking that a manual hybrid would otherwise need to wire.
-### Route SSR and first page event ownership
+### Route server preview and browser page commitment
-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.
+Legacy Next tracking can emit page events on the first route and route changes, while SSR helpers
+can also evaluate the first request. Remove legacy first-route calls as you adopt the target path.
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.
+prepares events, previews request state for server rendering, and creates a handoff. Mount its
+`NextAppAutoPageTracker` inside `optimization.request.OptimizationRoot`. The browser commits the
+matching prepared events through the SDK queue and tracks later navigation. The ordinary request
+family supplies the handoff and route inputs itself.
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'`.
+`createRequestHandoff(context, options)` inside `getServerSideProps`. The server preview supplies
+selections for useful first paint when it succeeds; the handoff retains prepared events for browser
+delivery even if the preview fails.
-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.
+Pass that Pages Router handoff to `OptimizationRoot`. On the matching route, the browser admits the
+prepared events to its queue. The tracker handles routes without a handoff and later navigation.
### Route cookie and profile continuity
@@ -126,18 +127,18 @@ 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.
+- The server preview and browser event delivery are verified separately.
+- The App Router request family supplies its handoff to the browser automatically; explicit paths
+ pass a matching route key and handoff.
- 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. |
+| Symptom | Check |
+| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
+| First page events duplicate | Remove legacy page calls and pass the same handoff and route to the browser root. |
+| 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
diff --git a/documentation/guides/forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md b/documentation/guides/forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md
index 75123cdef..c1dbfe48e 100644
--- a/documentation/guides/forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md
+++ b/documentation/guides/forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md
@@ -648,8 +648,9 @@ Then confirm the broader tracking contract:
- The third-party destination receives only the intended `contentful_*` fields.
- Sticky view tracking produces one intended downstream exposure, not one Experience exposure plus
one Insights exposure.
-- Server-rendered first paint and browser follow-up tracking have one owner for each event in the
- tracking plan.
+- On paired server-rendered pages, the server previews prepared events for first paint and the
+ browser admits them through the SDK queue. Forward accepted browser events separately from the
+ server preview; later navigation uses normal browser page tracking.
- Known-user identity uses your application user ID. The Optimization profile ID is metadata only
when your policy allows forwarding it.
- Event-stream `optimization` enrichment is reduced to approved primitive fields, such as audience
diff --git a/documentation/guides/integrating-the-node-sdk-in-a-node-app.md b/documentation/guides/integrating-the-node-sdk-in-a-node-app.md
index 8fdc3a2e5..201e5b9ed 100644
--- a/documentation/guides/integrating-the-node-sdk-in-a-node-app.md
+++ b/documentation/guides/integrating-the-node-sdk-in-a-node-app.md
@@ -1031,11 +1031,83 @@ guidance.
Add `@contentful/optimization-web` when the browser also needs to participate after server render.
Use the Node SDK alone when the server chooses the variant and renders the full response.
+For a paired server and browser route, prepare any initial `identify` or `track` events followed by
+the page event with `requestOptimization.prepareRequestHandoff({ routeKey, initialEvents, page })`. The
+server previews the batch to select content without committing its events. Pass the returned
+`handoff` to the browser, then call the Web SDK's
+`hydrateAndTrackCurrentPage(handoff, { routeKey, buildPayload })` for the same visible route. That
+call admits a matching prepared batch to the browser queue; the queue sends it and retries delivery
+under the usual policy. If preview fails, render baseline content and retain the prepared replay for
+browser commitment. A page blocked by the effective event gate or invalid input produces no replay, so the browser uses an
+ordinary page event instead. Keep `routeKey` as the visible pathname plus search on both sides.
+
+The prepared batch holds at most 100 events total: up to 99 initial `identify` or `track` events
+followed by exactly one page, with at most 50 identifies. Invalid batches are rejected before the
+server preview request. Browser queue admission also requires capacity for the entire batch, so a
+rejection preserves accepted queued events and returns `{ accepted: false }`.
+
+The preview response can supply a profile ID for a first request. It is issued by the Experience
+API, not generated by the SDK. A browser commitment response can return a linked ID. Persist an
+available API-issued ID only when persistence consent permits it; an ID need not be available after
+a failed preview. The `accepted` result from browser admission means the complete batch entered the
+queue, not that HTTP delivery finished.
+
+**Adapt this to your use case:** in the Express handler from the quick start, use the existing
+request-context and cookie helpers defined above, including the cookie parser in
+[Persist profile identity between requests](#persist-profile-identity-between-requests).
+`app-consent` and `app-user-id` are app-owned
+cookies; replace them with your consent and identity policy. `req.originalUrl` supplies the
+visible pathname plus search. `getRequestContext()` supplies default page context; the optional
+`page` argument below adds or overrides properties for this prepared page. Render `data` through the entry-resolution path earlier in this
+guide, and pass the serialized handoff to the browser even when `data` is unavailable after a
+recoverable preview failure. This excerpt assumes the app uses EJS as its template renderer.
+
+```ts
+import { ANONYMOUS_ID_COOKIE } from '@contentful/optimization-node/constants'
+
+const appConsent = req.cookies?.['app-consent'] === 'accepted'
+if (!appConsent) {
+ res.render('page', { handoffJson: 'null', optimizationData: null })
+ return
+}
+
+const userId = req.cookies?.['app-user-id']
+const requestOptimization = optimization.forRequest({
+ consent: { events: true, persistence: true },
+ eventContext: getRequestContext(req, APP_LOCALE),
+ locale: APP_LOCALE,
+ profile: getProfileFromRequest(req),
+})
+const { data, handoff } = await requestOptimization.prepareRequestHandoff({
+ routeKey: req.originalUrl,
+ initialEvents: userId ? [{ type: 'identify', userId }] : undefined,
+ page: { properties: { path: req.path } },
+})
+
+const profileId = data?.profile.id ?? handoff.profileId
+if (profileId && requestOptimization.canPersistProfile) {
+ res.cookie(ANONYMOUS_ID_COOKIE, profileId, { path: '/', sameSite: 'lax' })
+}
+res.render('page', {
+ handoffJson: JSON.stringify(handoff).replace(/
+ <%- handoffJson %>
+
+```
+
1. Store the shared anonymous profile ID in `ANONYMOUS_ID_COOKIE` when consent permits persistence.
2. Leave the shared cookie readable by browser-side code in hybrid Node + Web SDK apps.
3. Initialize the Web SDK with the same Contentful space ID, environment, and application locale.
-4. Let browser code handle later client-side consent, page events, entry interactions, and live
- updates.
+4. Let browser code commit the prepared first-route batch and handle later client-side consent,
+ page events, entry interactions, and live updates.
The Node SDK does not provide browser live updates or a preview UI. Keep those concerns in
browser-side SDK code or app-owned Contentful preview tooling.
@@ -1058,7 +1130,8 @@ per-request API options.
types.
3. Use `onEventBlocked` for diagnostics when consent blocks a request-bound event call.
4. Use request-scoped `experienceOptions` and `insightsOptions` for advanced API behavior such as
- `preflight`, IP override, or a custom Insights `beacon` sender.
+ IP override or a custom Insights `beacon` sender. `prepareRequestHandoff()` selects preflight for
+ its preview internally; ordinary `forRequest()` calls have no preflight setting.
**Follow this pattern:**
@@ -1078,7 +1151,7 @@ const requestOptimization = optimization.forRequest({
consent: { events: false, persistence: false },
eventContext: getRequestContext(req, appLocale),
// Advanced Experience API options stay request-scoped in stateless runtimes.
- experienceOptions: { preflight: true },
+ experienceOptions: { ip: req.ip },
locale: appLocale,
profile: getProfileFromRequest(req),
})
diff --git a/documentation/guides/integrating-the-optimization-android-sdk-in-a-compose-app.md b/documentation/guides/integrating-the-optimization-android-sdk-in-a-compose-app.md
index 1a197888d..34932d8a2 100644
--- a/documentation/guides/integrating-the-optimization-android-sdk-in-a-compose-app.md
+++ b/documentation/guides/integrating-the-optimization-android-sdk-in-a-compose-app.md
@@ -1082,7 +1082,6 @@ val strictConfig = OptimizationConfig(
allowedEventTypes = emptyList(),
queuePolicy = QueuePolicy(
offlineMaxEvents = 100,
- onOfflineDrop = { event -> logDroppedOptimizationEvent(event) },
),
// Blocked events are for verification; they are not re-sent after later consent.
onEventBlocked = { blockedEvent -> logBlockedOptimizationEvent(blockedEvent) },
@@ -1102,8 +1101,9 @@ After initialization the SDK monitors network reachability and app lifecycle. A
(`ConnectivityManager`) calls `setOnline(...)` on connectivity changes and `flush()` on reconnect; an
`AppLifecycleHandler` (a `ProcessLifecycleOwner` observer) calls `flush()` when the app moves to the
background for a best-effort drain. Queues are in-memory only — there is no durable outbox — and the
-offline Experience buffer is capped at 100 events by default (tunable via `QueuePolicy.offlineMaxEvents`);
-nothing survives process death.
+offline Experience buffer is capped at 100 events by default (tunable via `QueuePolicy.offlineMaxEvents`).
+At capacity, incoming events are rejected and accepted events keep their order. Nothing survives
+process death.
1. Let the SDK manage normal network and lifecycle handling after `OptimizationRoot` initializes the
client.
diff --git a/documentation/guides/integrating-the-optimization-android-sdk-in-a-views-app.md b/documentation/guides/integrating-the-optimization-android-sdk-in-a-views-app.md
index 57202eecc..00b6e527b 100644
--- a/documentation/guides/integrating-the-optimization-android-sdk-in-a-views-app.md
+++ b/documentation/guides/integrating-the-optimization-android-sdk-in-a-views-app.md
@@ -1158,7 +1158,8 @@ path.
1. Add a `QueuePolicy` only when production telemetry needs queue bounds or delivery callbacks. The
offline Experience queue holds up to 100 events by default (tunable via
`QueuePolicy.offlineMaxEvents`); queues are in-memory only, with no durable outbox, and do not
- survive process death.
+ survive process death. At capacity, an incoming Experience event is rejected and accepted events
+ keep their order.
**Adapt this to your use case:**
@@ -1168,12 +1169,11 @@ path.
queuePolicy = QueuePolicy(
// Cap offline storage to the app's production delivery budget.
offlineMaxEvents = 100,
- onOfflineDrop = { event -> Log.w("Optimization", "Dropped offline event: ${event.context}") },
),
)
```
-2. Use queue callbacks for operational diagnostics, not for resending blocked or dropped events.
+2. Use active queue callbacks for flush diagnostics. `onOfflineDrop` is deprecated and inactive.
3. Keep Contentful entry caching in the application layer — the SDK does not cache CDA responses for
Views rendering; you own content caching with locale-aware keys.
4. Call `flush()` only for deliberate release, test, or lifecycle flows; the SDK already flushes on
diff --git a/documentation/guides/integrating-the-optimization-ios-sdk-in-a-swiftui-app.md b/documentation/guides/integrating-the-optimization-ios-sdk-in-a-swiftui-app.md
index dffb11c66..ccffeb373 100644
--- a/documentation/guides/integrating-the-optimization-ios-sdk-in-a-swiftui-app.md
+++ b/documentation/guides/integrating-the-optimization-ios-sdk-in-a-swiftui-app.md
@@ -1112,7 +1112,8 @@ After initialization the SDK monitors network reachability and app lifecycle. A
app-state handler is compiled in wherever UIKit can be imported, which is the case in an iOS app build,
so a SwiftUI-lifecycle app gets the resign-active flush too — you do not need to add your own.
Queues are in-memory only — there is no durable outbox — and the offline Experience buffer is capped
-at 100 events by default (tunable via `QueuePolicy.offlineMaxEvents`); nothing survives process death.
+at 100 events by default (tunable via `QueuePolicy.offlineMaxEvents`). At capacity, incoming events
+are rejected and accepted events keep their order. Nothing survives process death.
1. Keep one `OptimizationClient` alive for the app or scene lifetime so the in-memory queue can
survive transient network changes.
@@ -1120,7 +1121,7 @@ at 100 events by default (tunable via `QueuePolicy.offlineMaxEvents`); nothing s
network simulation.
3. Call `client.flush()` from app-owned shutdown or critical-flow checkpoints when policy requires a
best-effort delivery attempt before leaving the flow.
-4. Use the `QueuePolicy` callbacks when operations teams need telemetry for offline drops, flush
+4. Use the active `QueuePolicy` callbacks when operations teams need telemetry for flush
failures, circuit-open events, or recovery.
**Follow this pattern:**
diff --git a/documentation/guides/integrating-the-optimization-ios-sdk-in-a-uikit-app.md b/documentation/guides/integrating-the-optimization-ios-sdk-in-a-uikit-app.md
index 096de123a..ff64ffca3 100644
--- a/documentation/guides/integrating-the-optimization-ios-sdk-in-a-uikit-app.md
+++ b/documentation/guides/integrating-the-optimization-ios-sdk-in-a-uikit-app.md
@@ -1315,13 +1315,17 @@ lifecycle handler flushes on `willResignActive`.
1. Add `QueuePolicy` only when production telemetry needs queue limits or lifecycle callbacks. The
offline Experience queue holds up to 100 events by default (tunable via
- `QueuePolicy.offlineMaxEvents`); queues are in-memory only and do not survive process death.
-2. Use queue callbacks for operational diagnostics, not for resending blocked or dropped events.
+ `QueuePolicy.offlineMaxEvents`); queues are in-memory only and do not survive process death. At
+ capacity, an incoming Experience event is rejected and accepted events keep their order.
+2. Use active queue callbacks for flush diagnostics. `onOfflineDrop` is deprecated and inactive.
3. Keep Contentful entry caching in the application layer — the SDK does not cache CDA responses for
UIKit rendering.
4. Call `flush()` only for deliberate release, test, or lifecycle flows; the SDK already flushes on
background and reconnect.
+`diagnostics` is your app's operational logger. Each flush callback receives a `QueueEvent`; its
+`context` dictionary contains the details to record.
+
**Adapt this to your use case:**
```swift
@@ -1330,11 +1334,6 @@ let config = OptimizationConfig(
spaceId: "your-space-id",
queuePolicy: QueuePolicy(
offlineMaxEvents: 500,
- onOfflineDrop: { event in
- // event is a QueueEvent with a type and a context dictionary;
- // diagnostics is reader-owned: your own operational logging.
- diagnostics.record("optimization-offline-drop", context: event.context)
- },
onFlushFailure: { event in
diagnostics.record("optimization-flush-failure", context: event.context)
},
diff --git a/documentation/guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md b/documentation/guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md
index 8feb71d72..ffdeaee96 100644
--- a/documentation/guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md
+++ b/documentation/guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md
@@ -10,8 +10,9 @@ fern:
# Integrate the Optimization Next.js SDK in a Next.js App Router app
Use this guide to render a personalized Contentful entry on the server and keep the same result when
-the browser starts. The server gives the browser a plain-data snapshot of the selected variants and
-browser startup settings used for that render. This snapshot is an **Optimization handoff**.
+the browser starts. The server previews the initial events to choose content, then gives the browser
+a plain-data **Optimization handoff** with those prepared events, selected variants, and startup
+settings. The browser commits the events for that visit.
**New to personalization?** Here is the whole idea in four points:
@@ -82,7 +83,8 @@ personalization path.
Use the same Contentful environment for server and client binding code. The consent values below
are a quick-start policy shortcut: `server.events` and `clientDefaults.consent` allow
personalization events, while `server.persistence` and
- `clientDefaults.persistenceConsent` allow the SDK-owned anonymous ID to persist. The
+ `clientDefaults.persistenceConsent` allow an Experience API-issued anonymous ID to persist in the
+ SDK-owned cookie. The
`preserve-server` hydration mode tells the browser to keep the server-rendered result while its
live runtime starts.
@@ -365,32 +367,53 @@ export const clientOptimization = bindNextjsAppRouterClientOptimization({
})
```
-As an optional client-only path, add `beforeInitialPage` when the request root must finish returned
-identity or custom Experience event work before its first browser-owned page decision. Keep the
-callback in this `'use client'` module. Here, **identity** means the visitor ID and traits your
-application is allowed to send; the full lifecycle is covered in
-[Consent, identity, profile, and reset](#consent-identity-profile-and-reset).
-
-The **initial page decision** is the request root's one choice to send the first browser `page`
-event or skip it because the server handoff already owns that route. The server request family can
-accept its page event and serialize that ownership in the handoff. The browser request root applies
-the handoff; after its live owned runtime exists, it invokes the callback, makes one direct page
-attempt or same-route handoff skip, marks the attempted route, and emits for later route changes.
-
-After that live owned runtime exists, the callback runs once during a retained root lifetime that
-ends when the request root unmounts. A real remount starts another lifetime. The SDK-provided
-`BeforeInitialPageClient` exposes methods that stay bound when destructured: `identify` supplies
-visitor identity, `screen` records a screen-view Experience event, and `track` sends an app-named
-custom Experience event.
-
-Return one value that represents all before-initial-page operations. A JavaScript `Promise`
-represents work that finishes later; a **thenable** is a Promise-like object with a `.then()` method.
-An `async` callback returns one Promise automatically, and every operation you `await` becomes part
-of that returned work.
-
-**Adapt this to your use case:** extend the client binding above. `app-user-id` and `client_ready`
-are app-owned identifiers in this example; replace them with the browser identity store and custom
-event name your app owns.
+For request-family routes, any identity or custom Experience events that must precede the first
+page belong in the server binding's `request.initialEvents`. The SDK prepares those events in order,
+followed by the page event, and previews the batch for the server render. This preview selects
+content but does not commit the events. The browser receives the prepared replay through the
+handoff and admits it to the Experience queue for the matching route. Use app-owned values from the
+request context only when your consent policy permits them.
+
+Keep the prepared batch to 100 events total, including exactly one final page and at most 50
+identifies. The SDK validates it before the preview request; an invalid batch has no replay. In the
+browser, the whole batch needs room in the configured Experience queue before it is admitted.
+
+**Adapt this to your use case:** derive per-request inputs from your app-owned consent and identity
+cookies. Replace `app-consent`, `app-user-id`, and `server_render` with your policy and event name.
+Apply the same consent rule in `consent.server`; checking the cookie here does not grant SDK event
+consent by itself. Keep the binding and client module in separate files.
+
+```diff
+ // lib/optimization.ts
+ export const optimization = bindNextjsAppRouterServerOptimization({
+ // your existing server config
+- request: { hydration: 'preserve-server' },
++ request: {
++ hydration: 'preserve-server',
++ initialEvents: ({ cookies }) => {
++ if (cookies.get('app-consent')?.value !== 'accepted') return []
++ const userId = cookies.get('app-user-id')?.value
++ return [
++ ...(userId ? [{ type: 'identify' as const, userId }] : []),
++ { type: 'track' as const, event: 'server_render' },
++ ]
++ },
++ },
+ })
+```
+
+For an optional browser-only route **without prepared replay**, the client binder accepts
+`beforeInitialPage`. It waits for returned browser identity or custom Experience work before its
+direct page attempt. A request-family root with prepared replay takes the normal handoff queue path
+and bypasses this callback. Keep `request.initialEvents` above for inputs that must participate in
+server evaluation.
+
+**Adapt this to your use case:** extend the existing `'use client'` binding. `app-user-id` and
+`client_ready` are app-owned identifiers; replace them with your identity store and event name.
+The `async` callback returns one Promise that includes both awaited operations. The SDK's
+`identify`, `screen`, and `track` delegates are safe to destructure; `screen` records a screen view.
+The consent defaults below let this browser-only route demonstrate accepted events; replace them
+with your application consent policy before shipping.
```diff
// lib/optimization-client.ts
@@ -401,104 +424,87 @@ event name your app owns.
+ bindNextjsAppRouterClientOptimization,
+ type BeforeInitialPageOptions,
+} from '@contentful/optimization-nextjs/app-router/client'
-
-+const APP_USER_ID_KEY = 'app-user-id'
-+const CLIENT_READY_EVENT = 'client_ready'
+
+const beforeInitialPage = {
+ run: async ({ identify, track }) => {
-+ const userId = window.localStorage.getItem(APP_USER_ID_KEY)
++ const userId = window.localStorage.getItem('app-user-id')
+ if (userId !== null) await identify({ userId })
-+ await track({ event: CLIENT_READY_EVENT })
++ await track({ event: 'client_ready' })
+ },
+ onError: (error) => console.warn('Before-initial-page work failed.', error),
+} satisfies BeforeInitialPageOptions
-+
+
export const clientOptimization = bindNextjsAppRouterClientOptimization({
spaceId: process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID!,
environment: process.env.NEXT_PUBLIC_CONTENTFUL_ENVIRONMENT ?? 'master',
locale: 'en-US',
++ consent: { clientDefaults: { consent: true, persistenceConsent: true } },
+ beforeInitialPage,
})
+
-+export const {
-+ RequestOptimizationRoot: ClientRequestOptimizationRoot,
-+} = clientOptimization
++export const { OptimizationRoot: ClientOptimizationRoot } = clientOptimization
```
-The client binding with before-initial-page work exposes two content roots for different owners:
+Mount this direct client root on a route outside the `(request)` layout from the quick start, so
+there is no ancestor request root or prepared handoff. The `routeKey` is the visible pathname plus
+search and changes with navigation. `buildPagePayload` is lazy, so the root reads current route
+values after the callback. The Next.js `useSearchParams` hook needs a `Suspense` boundary; preserve
+a meaningful fallback. The surrounding route is illustrative context to adapt to your own app.
-- `clientOptimization.OptimizationRoot` is for direct Client Component composition. It requires an
- explicit `routeKey` and lazy `buildPagePayload`. It does not accept `initialPagePayload`, the
- eager page data object computed before later route changes.
-- `ClientRequestOptimizationRoot` is the `RequestOptimizationRoot` component for the server
- binding's request family. It accepts only serializable request-root values: children, defaults,
- the content handoff, and hydration mode. It derives the live route and lazy page payload in the
- client, so callers do not pass route or page-payload props.
+**Adapt this to your use case:** render a browser-only page with no separate auto-page tracker.
+Replace the placeholder page body with your existing content. The development observer is defined
+in [The bound root and page events](#the-bound-root-and-page-events); remove it after checking order.
-Inject the second component into the existing server binding. The component reference replaces the
-default browser root only for `optimization.request`; it does not wrap or create a second root.
-
-**Adapt this to your use case:** complete this server binding and layout continuation as one change.
-Add the request-root composition argument, stop exporting and importing the normal request tracker,
-and remove its JSX before you run the route with `beforeInitialPage`. Keep the existing server
-config and other request-family exports.
-
-```diff
- // lib/optimization.ts
- import { bindNextjsAppRouterServerOptimization } from '@contentful/optimization-nextjs/app-router/server'
-+import { ClientRequestOptimizationRoot } from './optimization-client'
- import { contentfulClient } from './contentful'
+```tsx
+// app/browser-only/page.tsx
+'use client'
- export const optimization = bindNextjsAppRouterServerOptimization({
- // your existing server config
--})
-+}, {
-+ request: { OptimizationRoot: ClientRequestOptimizationRoot },
-+})
-
- export const {
-- NextAppAutoPageTracker: RequestNextAppAutoPageTracker,
- OptimizationRoot: RequestOptimizationRoot,
- OptimizedEntry: RequestOptimizedEntry,
- } = optimization.request
-```
+import { ClientOptimizationRoot } from '@/lib/optimization-client'
+import { OptimizationEventDiagnostics } from '@/components/OptimizationEventDiagnostics'
+import Link from 'next/link'
+import { usePathname, useSearchParams } from 'next/navigation'
+import { Suspense } from 'react'
-**Adapt this to your use case:** continue immediately in the request layout. The surrounding layout
-remains app-owned context; remove both the tracker import and its JSX.
+function BrowserOnlyRoute() {
+ const pathname = usePathname()
+ const search = useSearchParams()?.toString() ?? ''
+ const routeKey = `${pathname}${search ? `?${search}` : ''}`
-```diff
- // app/(request)/layout.tsx
--import { RequestNextAppAutoPageTracker, RequestOptimizationRoot } from '@/lib/optimization'
-+import { RequestOptimizationRoot } from '@/lib/optimization'
+ return (
+ Browser-only route
Browser-only route
+ Next view +Consent: {consent === true ? 'Yes' : 'No'}
+Latest event: {latestEvent?.type ?? 'None'}
+ +{entry.text}
+{entry.text}
+