diff --git a/documentation/authoring/blueprints/nextjs-app-router.md b/documentation/authoring/blueprints/nextjs-app-router.md index 63fa42b8c..574f450ba 100644 --- a/documentation/authoring/blueprints/nextjs-app-router.md +++ b/documentation/authoring/blueprints/nextjs-app-router.md @@ -43,34 +43,37 @@ 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 bound factory and server/client entry points before deeper configuration. | Runtime-specific entry points, the one-factory boundary, and which initial settings are SDK mechanism versus application policy. | [package](../../internal/sdk-knowledge/web/nextjs-app-router.md#package--entry-points), [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--factory) | -| Fetching Contentful entries | Required for first integration | Establish the app-fetched-versus-SDK-managed entry boundary before personalization. | One developed app-fetched example and one managed example that connects server rendering, root prefetch, and browser rendering. | [App Router setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--factory), [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 a request receives visitor context and where application policy enters. | Request/profile concepts, the Next.js handler choice, cookie ownership and consent, plus a server-observable verification path. | [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 entry-to-component handoff that produces Milestone 1. | A representative renderer diff, render-prop typing, baseline behavior, and the dynamic-render and request-specific-cache consequences. | [rendering](../../internal/sdk-knowledge/web/nextjs-app-router.md#render--entry-resolution), [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--factory), [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 the server-state handoff and ownership of the first page event. | Root and tracker placement, first-event ownership, and a mounted diagnostic that distinguishes browser events from blocked attempts. | [components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking), [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [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--factory), [components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [React Web setup](../../internal/sdk-knowledge/web/react-web.md#setup--factory), [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, factory 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 for the supported route strategies. | [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) | -| Manual server and client escape hatches | Advanced or production-only | Expose lower-level APIs only after the bound path is understood. | A precise API and ownership map; show a complete manual request-to-provider flow only when the guide recommends that path. | [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) | -| Caching and request deduplication | Advanced or production-only | Prevent profile-specific output from entering shared caches. | A cache-safety table, the distinction between deduplication/batching and shared caching, and a two-profile validation. | [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) | -| 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, 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 bound factory and server/client entry points before deeper configuration. | Runtime-specific entry points, the one-factory boundary, and which initial settings are SDK mechanism versus application policy. | [package](../../internal/sdk-knowledge/web/nextjs-app-router.md#package--entry-points), [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--factory) | +| Fetching Contentful entries | Required for first integration | Establish the app-fetched-versus-SDK-managed entry boundary before personalization. | One developed app-fetched example and one managed example that connects server rendering, root prefetch, and browser rendering. | [App Router setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--factory), [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 a request receives visitor context and where application policy enters. | Request/profile concepts, the Next.js handler choice, cookie ownership and consent, plus a server-observable verification path. | [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 entry-to-component handoff that produces Milestone 1. | A representative renderer diff, render-prop typing, baseline behavior, and the dynamic-render and request-specific-cache consequences. | [rendering](../../internal/sdk-knowledge/web/nextjs-app-router.md#render--entry-resolution), [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--factory), [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 request handoff and ownership of the first page event. | Root and tracker placement, first-event ownership, and a mounted diagnostic that distinguishes browser events from blocked attempts. | [components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking), [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [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--factory), [components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [React Web setup](../../internal/sdk-knowledge/web/react-web.md#setup--factory), [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, factory 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; 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 bound path is understood. | A precise API and ownership map; show a complete manual request-to-provider flow only when the guide recommends that path. | [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) | +| Caching and request deduplication | Advanced or production-only | Prevent profile-specific output from entering shared caches. | A cache-safety table, the distinction between deduplication/batching and shared caching, links to the handoff concept and supplemental rendering guide, and a two-profile validation. | [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, 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 - Put the request-handler filename/export choice in the quick start and repeat the failure symptom in - Troubleshooting; it can silently prevent all server state. Facts: + Troubleshooting; it can silently prevent request context from reaching the handoff. Facts: [runtime quirks](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks). - Put the dynamic-render consequence in the first-paint Core section, not only in caching. Facts: [runtime quirks](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks). - Keep “Browser takeover and live updates” in Core because the handoff model is required knowledge, but distinguish that from enabling live updates, which is opt-in. Facts: [components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks). +- Keep the integration guide focused on request handoff and route ownership. Static, ISR, edge, and + analytics-only recipes live in the supplemental rendering guide; deeper handoff mechanics live in + the concept. ## Troubleshooting scope @@ -88,7 +91,9 @@ guide: ../../guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app. ## Link roles - [The sibling Next.js Pages Router integration guide](../../guides/integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md). +- [The Next.js rendering supplemental guide](../../guides/rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.md). - [The analytics-forwarding supplemental guide](../../guides/forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md). +- [Optimization handoff and cache-safe rendering](../../concepts/optimization-handoff-and-cache-safe-rendering.md). - [Entry personalization and variant resolution](../../concepts/entry-personalization-and-variant-resolution.md). - [Locale handling](../../concepts/locale-handling-in-the-optimization-sdk-suite.md). - [Profile synchronization](../../concepts/profile-synchronization-between-client-and-server.md). diff --git a/documentation/authoring/blueprints/nextjs-pages-router.md b/documentation/authoring/blueprints/nextjs-pages-router.md index 3246181de..20d17f665 100644 --- a/documentation/authoring/blueprints/nextjs-pages-router.md +++ b/documentation/authoring/blueprints/nextjs-pages-router.md @@ -27,28 +27,28 @@ guide: ../../guides/integrating-the-optimization-sdk-in-a-nextjs-pages-router-ap - **Milestone 1:** Server-resolved first paint and matching hydration. - **Milestone 2:** Opt-in browser re-personalization after hydration. -- **Boundary:** Serialized server state versus live browser-owned state. +- **Boundary:** Serialized request handoff versus live browser-owned state. - **Fact sources:** [runtime](../../internal/sdk-knowledge/web/nextjs-pages-router.md#version--runtime-quirks). ## 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; two factory 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. | Both paths; locale/include rules; client ownership. | [rendering](../../internal/sdk-knowledge/web/nextjs-pages-router.md#render--entry-resolution) | -| The getServerSideProps state handoff and the profile cookie | Required for first integration | Teach how server decisions reach `_app.tsx`. | Complete props merge; failure handling; cookie ownership; explicit no-middleware note. | [setup](../../internal/sdk-knowledge/web/nextjs-pages-router.md#setup--factory), [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. | `_app.tsx` example; tracker example; emit-versus-skip explanation. | [components](../../internal/sdk-knowledge/web/nextjs-pages-router.md#components--hooks), [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking) | -| Personalizing entries | Required for first integration | Complete Milestone 1 at the renderer boundary. | Render-prop diff; cast/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. | [runtime](../../internal/sdk-knowledge/web/nextjs-pages-router.md#version--runtime-quirks) | -| 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 and request failure policy. | [runtime](../../internal/sdk-knowledge/web/nextjs-pages-router.md#version--runtime-quirks) | -| 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; two factory 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. | Both paths; locale/include rules; client ownership. | [rendering](../../internal/sdk-knowledge/web/nextjs-pages-router.md#render--entry-resolution) | +| The getServerSideProps request handoff and the profile cookie | Required for first integration | Teach how server decisions reach `_app.tsx`. | Complete props merge; failure handling; cookie ownership; explicit no-middleware note. | [setup](../../internal/sdk-knowledge/web/nextjs-pages-router.md#setup--factory), [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. | `_app.tsx` example; tracker example; emit-versus-skip explanation. | [components](../../internal/sdk-knowledge/web/nextjs-pages-router.md#components--hooks), [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking) | +| Personalizing entries | Required for first integration | Complete Milestone 1 at the renderer boundary. | Render-prop diff; cast/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 @@ -56,6 +56,9 @@ guide: ../../guides/integrating-the-optimization-sdk-in-a-nextjs-pages-router-ap proxy. Facts: [runtime](../../internal/sdk-knowledge/web/nextjs-pages-router.md#version--runtime-quirks). - Treat the props merge and Experience API failure path as teaching requirements, not incidental notes. Facts: [setup](../../internal/sdk-knowledge/web/nextjs-pages-router.md#setup--factory). +- 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. ## Troubleshooting scope @@ -69,6 +72,8 @@ guide: ../../guides/integrating-the-optimization-sdk-in-a-nextjs-pages-router-ap ## Link roles - [The sibling Next.js App Router integration guide](../../guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md). +- [The Next.js rendering supplemental guide](../../guides/rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.md). - [The analytics-forwarding supplemental guide](../../guides/forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md). +- [Optimization handoff and cache-safe rendering](../../concepts/optimization-handoff-and-cache-safe-rendering.md). - [The maintained Pages Router reference implementation](../../../implementations/nextjs-sdk_pages-router/README.md). - [The maintained App Router reference implementation for comparison](../../../implementations/nextjs-sdk_app-router/README.md). diff --git a/documentation/concepts/README.md b/documentation/concepts/README.md index bc301b0e6..a0ddb1cb4 100644 --- a/documentation/concepts/README.md +++ b/documentation/concepts/README.md @@ -8,6 +8,7 @@ children: - ./interaction-tracking-in-web-sdks.md - ./interaction-tracking-in-node-and-stateless-environments.md - ./profile-synchronization-between-client-and-server.md + - ./optimization-handoff-and-cache-safe-rendering.md - ./react-native-sdk-interaction-tracking-mechanics.md - ./ios-sdk-runtime-and-interaction-mechanics.md - ./android-sdk-runtime-and-interaction-mechanics.md @@ -46,6 +47,9 @@ they are not the first stop for installation or setup commands. - [Profile synchronization between client and server](./profile-synchronization-between-client-and-server.md) - explains how profile identity, profile data, cookies, browser storage, and Experience API responses work together when Node and Web SDK runtimes share a visitor journey. +- [Optimization handoff and cache-safe rendering](./optimization-handoff-and-cache-safe-rendering.md) - + explains how server, static, ISR, and edge-rendered Optimization state reaches the browser, how + cache scopes work, and why request profile state must not enter public caches. - [React Native SDK interaction tracking mechanics](./react-native-sdk-interaction-tracking-mechanics.md) - explains how the React Native SDK observes, gates, and emits tracking events, covering event types, the viewport state machine, default visibility and timing, consent gating, scroll context, diff --git a/documentation/concepts/consent-management-in-the-optimization-sdk-suite.md b/documentation/concepts/consent-management-in-the-optimization-sdk-suite.md index cc4231f55..43d8afa3d 100644 --- a/documentation/concepts/consent-management-in-the-optimization-sdk-suite.md +++ b/documentation/concepts/consent-management-in-the-optimization-sdk-suite.md @@ -10,9 +10,9 @@ consent management platform (CMP) decisions into SDK configuration, and how comm expectations affect integration choices. This document applies to the Web, React Web, Next.js adapter, Node, React Native, iOS, and Android -SDKs. Next.js server and ESR paths follow Node consent mechanics; Next.js client components follow -React Web consent mechanics. The Next.js context handler is request-context forwarding only; it is -not a consent path. This document uses these terms: +SDKs. Next.js server and edge paths follow Node consent mechanics; Next.js client components follow +React Web consent mechanics. Request-context forwarding is not a consent path. This document uses +these terms: - **Event consent** - The SDK runtime value that gates Optimization event emission. - **Persistence consent** - The SDK runtime value that gates durable profile-continuity storage. @@ -83,26 +83,26 @@ the affected method calls or forwarding code in the application layer. Use the runtime surface that matches where the consent decision is applied: -| Runtime | Consent API surface | Storage and persistence | Default allow-list | Blocked-event diagnostics | -| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | -| **Web** | `defaults.consent`, `defaults.persistenceConsent`, and `optimization.consent(true \| false \| { events, persistence })` | Browser `localStorage`; readable `ctfl-opt-aid` cookie for profile continuity when persistence consent permits it. | `identify` and `page` can emit before event consent unless `allowedEventTypes` changes. | `onEventBlocked` and `optimization.states.blockedEventStream` | -| **React Web** | `OptimizationRoot` `defaults` and `useOptimizationActions().setConsent(true \| false \| { events, persistence })`; injected SDKs can call `sdk.consent(...)` | Same Web SDK storage: browser `localStorage` and the readable `ctfl-opt-aid` cookie when persistence consent permits it. | `identify` and `page` can emit before event consent unless `allowedEventTypes` changes. | `onEventBlocked` and `states.blockedEventStream` | -| **Next.js** | Preferred App Router `createNextjsAppRouterOptimization({ server: { enabled: true, consent } })`; Pages Router `getServerSideOptimizationProps()`; manual server `getNextjsServerOptimizationData(..., { consent })`; ESR `getNextjsEsrOptimizationData(..., { consent })`; and client `OptimizationRoot` | Server and ESR paths use application-owned cookies and request state; client paths use React Web storage. | Server, ESR, and client paths follow `identify` and `page` defaults unless `allowedEventTypes` changes. | Server-side `onEventBlocked`; client `onEventBlocked` and `states.blockedEventStream` | -| **Node** | Request-scoped `optimization.forRequest({ consent })` | No SDK storage; applications own cookies, sessions, consent records, and profile ID persistence. | `identify` and `page` can emit before request event consent unless `allowedEventTypes` changes. | `onEventBlocked` only | -| **React Native** | `OptimizationRoot` `defaults` and `useOptimization().consent(true \| false \| { events, persistence })` | AsyncStorage persists consent and, when persistence consent permits it, profile-continuity state across launches. | `identify` and `screen` can emit before event consent unless `allowedEventTypes` changes. | `onEventBlocked` and `states.blockedEventStream` | -| **iOS** | `StorageDefaults(consent:)`, `client.consent(_:)`, and `client.consent(events:persistence:)` | UserDefaults persists consent and, when persistence consent permits it, profile-continuity state across launches. | `identify` and `screen` can emit before event consent unless `allowedEventTypes` changes. | `OptimizationConfig.onEventBlocked` and `client.blockedEventStream` | -| **Android** | `StorageDefaults(consent = true)`, `client.consent(true \| false)`, and `client.consent(events = true, persistence = false)` | SharedPreferences persists consent and, when persistence consent permits it, profile-continuity state across launches. | `identify` and `screen` can emit before event consent unless `allowedEventTypes` changes. | `OptimizationConfig.onEventBlocked` and `client.blockedEventStream` | +| Runtime | Consent API surface | Storage and persistence | Default allow-list | Blocked-event diagnostics | +| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | +| **Web** | `defaults.consent`, `defaults.persistenceConsent`, and `optimization.consent(true \| false \| { events, persistence })` | Browser `localStorage`; readable `ctfl-opt-aid` cookie for profile continuity when persistence consent permits it. | `identify` and `page` can emit before event consent unless `allowedEventTypes` changes. | `onEventBlocked` and `optimization.states.blockedEventStream` | +| **React Web** | `OptimizationRoot` `defaults` and `useOptimizationActions().setConsent(true \| false \| { events, persistence })`; injected SDKs can call `sdk.consent(...)` | Same Web SDK storage: browser `localStorage` and the readable `ctfl-opt-aid` cookie when persistence consent permits it. | `identify` and `page` can emit before event consent unless `allowedEventTypes` changes. | `onEventBlocked` and `states.blockedEventStream` | +| **Next.js** | App Router and Pages Router factories with `consent.server` and `consent.clientDefaults`; request handoff helpers such as `createRequestHandoff(...)`; edge runtime `createEdgeRequestHandoff(...)`; manual server `getNextjsServerOptimizationData(..., { consent })`; and client `OptimizationRoot` | Server and edge paths use application-owned cookies and request state; client paths use React Web storage. | Server, edge, and client paths follow `identify` and `page` defaults unless `allowedEventTypes` changes. | Server-side `onEventBlocked`; client `onEventBlocked` and `states.blockedEventStream` | +| **Node** | Request-scoped `optimization.forRequest({ consent })` | No SDK storage; applications own cookies, sessions, consent records, and profile ID persistence. | `identify` and `page` can emit before request event consent unless `allowedEventTypes` changes. | `onEventBlocked` only | +| **React Native** | `OptimizationRoot` `defaults` and `useOptimization().consent(true \| false \| { events, persistence })` | AsyncStorage persists consent and, when persistence consent permits it, profile-continuity state across launches. | `identify` and `screen` can emit before event consent unless `allowedEventTypes` changes. | `onEventBlocked` and `states.blockedEventStream` | +| **iOS** | `StorageDefaults(consent:)`, `client.consent(_:)`, and `client.consent(events:persistence:)` | UserDefaults persists consent and, when persistence consent permits it, profile-continuity state across launches. | `identify` and `screen` can emit before event consent unless `allowedEventTypes` changes. | `OptimizationConfig.onEventBlocked` and `client.blockedEventStream` | +| **Android** | `StorageDefaults(consent = true)`, `client.consent(true \| false)`, and `client.consent(events = true, persistence = false)` | SharedPreferences persists consent and, when persistence consent permits it, profile-continuity state across launches. | `identify` and `screen` can emit before event consent unless `allowedEventTypes` changes. | `OptimizationConfig.onEventBlocked` and `client.blockedEventStream` | ## Choose a pre-consent posture There are three common implementation postures. Choose the runtime column that matches where the SDK is initialized; server paths also bind consent on each request: -| Posture | Web, React Web, and React Native | iOS | Android | Node and Next.js server paths | Use when | -| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| **Strict opt-in** | Initialize with `allowedEventTypes: []`; leave `defaults.consent` unset and persistence consent unset or false. | Pass `allowedEventTypes: []`; do not pass `StorageDefaults(consent: true)`; keep persistence consent unset or false. | Pass `allowedEventTypes = emptyList()`; do not pass `StorageDefaults(consent = true)`; keep persistence consent false. | Configure the singleton with `allowedEventTypes: []`; bind `forRequest({ consent: { events: false, persistence: false } })`, set Next App Router `server.consent` to the same value, or pass the equivalent server/ESR helper option. | Policy does not permit non-essential event emission or durable profile-continuity storage before opt-in. | -| **Limited pre-consent context** | Leave consent unset; use the runtime default or custom `allowedEventTypes`; keep persistence consent unset or false. | Don't pass `StorageDefaults(consent: true)`; pass a narrow `allowedEventTypes` list; keep persistence consent false. | Don't pass `StorageDefaults(consent = true)`; pass a narrow `allowedEventTypes` list; keep persistence consent false. | Configure a narrow singleton `allowedEventTypes`; derive consent for `forRequest({ consent })`, Next App Router `server.consent`, or server/ESR helper options from application request state. | Legal review permits specific first-party context events before broader tracking consent. | -| **Default-on accepted context** | Seed `defaults.consent: true`; set `defaults.persistenceConsent: false` only when profile continuity must be deferred. | Seed `StorageDefaults(consent: true)`; set `persistenceConsent: false` only when profile continuity must be deferred. | Seed `StorageDefaults(consent = true)`; set `persistenceConsent = false` only when profile continuity must be deferred. | Bind accepted request consent with `forRequest({ consent: { events: true, persistence: true } })`, set Next App Router `server.consent` to accepted consent, or pass the equivalent server/ESR helper option. | Application policy permits SDK event emission and profile continuity at startup, with or without a separate end-user consent UI. | +| Posture | Web, React Web, and React Native | iOS | Android | Node and Next.js server paths | Use when | +| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| **Strict opt-in** | Initialize with `allowedEventTypes: []`; leave `defaults.consent` unset and persistence consent unset or false. | Pass `allowedEventTypes: []`; do not pass `StorageDefaults(consent: true)`; keep persistence consent unset or false. | Pass `allowedEventTypes = emptyList()`; do not pass `StorageDefaults(consent = true)`; keep persistence consent false. | Configure the singleton with `allowedEventTypes: []`; bind `forRequest({ consent: { events: false, persistence: false } })`, set Next.js `consent.server` to the same value, or pass the equivalent manual server request option. | Policy does not permit non-essential event emission or durable profile-continuity storage before opt-in. | +| **Limited pre-consent context** | Leave consent unset; use the runtime default or custom `allowedEventTypes`; keep persistence consent unset or false. | Don't pass `StorageDefaults(consent: true)`; pass a narrow `allowedEventTypes` list; keep persistence consent false. | Don't pass `StorageDefaults(consent = true)`; pass a narrow `allowedEventTypes` list; keep persistence consent false. | Configure a narrow singleton `allowedEventTypes`; derive consent for `forRequest({ consent })`, Next.js `consent.server`, or manual server request options from application request state. | Legal review permits specific first-party context events before broader tracking consent. | +| **Default-on accepted context** | Seed `defaults.consent: true`; set `defaults.persistenceConsent: false` only when profile continuity must be deferred. | Seed `StorageDefaults(consent: true)`; set `persistenceConsent: false` only when profile continuity must be deferred. | Seed `StorageDefaults(consent = true)`; set `persistenceConsent = false` only when profile continuity must be deferred. | Bind accepted request consent with `forRequest({ consent: { events: true, persistence: true } })`, set Next.js `consent.server` to accepted consent, or pass the equivalent manual server request option. | Application policy permits SDK event emission and profile continuity at startup, with or without a separate end-user consent UI. | For browser applications, storage policy can matter as much as event policy. `allowedEventTypes: []` prevents pre-consent event emission, while persistence consent gates durable profile-continuity @@ -179,10 +179,10 @@ unset and call the runtime's boolean or split-consent API from the CMP or banner ### Node and stateless runtimes -The Node SDK does not store consent. Next.js server and ESR paths use this same request-scoped model -through adapter helpers. For App Router integrations, prefer the `/app-router` factory and put the -request policy in `server.consent`. For Pages Router `getServerSideProps`, pass the same policy to -`getServerSideOptimizationProps()`: +The Node SDK does not store consent. Next.js server and edge paths use this same request-scoped +model through adapter helpers. For App Router integrations, prefer the `/app-router` factory and put +the request policy in `consent.server`. For Pages Router `getServerSideProps`, use the +`/pages-router/server` factory and put the same policy in `consent.server`: Next.js App Router / TypeScript: @@ -190,17 +190,17 @@ Next.js App Router / TypeScript: createNextjsAppRouterOptimization({ clientId: 'your-client-id', environment: 'main', - server: { enabled: true, consent }, + consent: { server: consent }, }) ``` -`server.consent` can be a boolean, object-form consent, or resolver that receives -`{ cookies, headers }`. The bound server root or provider resolves it, calls the server page path, -and passes consent-derived client defaults and server optimization state through. Manual -`getNextjsServerOptimizationData(..., { consent })` and ESR -`getNextjsEsrOptimizationData(..., { consent })` remain lower-level paths when the application owns -request binding directly. When the application policy permits Optimization by default, bind each -request with accepted event and persistence consent: +`consent.server` can be a boolean, object-form consent, or resolver that receives +`{ cookies, headers }`. Request handoff helpers resolve it, call the server page path, and return a +browser handoff. `consent.clientDefaults` seeds browser defaults when configured. Manual +`getNextjsServerOptimizationData(..., { consent })`, manual `createNextjsRequestHandoff(...)` with a +`consent` option, and edge `createEdgeRequestHandoff(...)` remain lower-level paths when the +application owns request binding directly. When the application policy permits Optimization by +default, bind each request with accepted event and persistence consent: Node / TypeScript: @@ -401,8 +401,9 @@ server and the Web or React Web SDK in the browser. Use the same consent decisio - The server reads consent before calling `page()`, `identify()`, or follow-up tracking methods and calls the Node SDK only when the application policy allows the server event. -- Next.js App Router bound components read consent from `server.consent`; manual server and ESR - paths pass the same decision through their `{ consent }` option. +- Next.js App Router and Pages Router helpers read consent from `consent.server`; manual server + paths pass the same decision through their `{ consent }` option, and edge helpers read the same + policy from their `consent.server` config. - The browser calls `consent(true)` or `consent(false)` from the same CMP state after hydration. - Both runtimes clear profile continuity when withdrawal requires it. - The server does not set `ctfl-opt-aid` when the browser is not allowed to use profile continuity. @@ -485,10 +486,9 @@ Before releasing a consent-aware Optimization SDK integration, verify these impl - Bind Node SDK calls with `forRequest()`; use `allowedEventTypes` only for intentionally permitted pre-consent server events, and never persist returned IDs unless `requestOptimization.canPersistProfile` is true. -- For Next.js App Router, put the same request policy in `server.consent` on - `createNextjsAppRouterOptimization()`. For Pages Router, pass it to - `getServerSideOptimizationProps()`. Use manual server or ESR `{ consent }` options only when - direct request binding is needed. +- For Next.js App Router and Pages Router, put the same request policy in `consent.server` on the + relevant factory. Use manual server request options or the edge request helper only when direct + request binding is needed. - Clear active in-memory profile state with the runtime reset method and server profile cookies when revocation requires it. - Use JavaScript or React Native `consent({ events, persistence })`, iOS @@ -527,7 +527,7 @@ Before releasing a consent-aware Optimization SDK integration, verify these impl - [Interaction tracking in Node and stateless environments](./interaction-tracking-in-node-and-stateless-environments.md) - Request-scoped consent, stateless tracking, and server-side profile persistence. - [Next.js SDK README](../../packages/web/frameworks/nextjs-sdk/README.md) - Adapter boundaries for - server consent, ESR consent and persistence, request-context forwarding, and client consent. + server consent, edge consent and persistence, request-context forwarding, and client consent. - [React Native SDK interaction tracking mechanics](./react-native-sdk-interaction-tracking-mechanics.md) - React Native consent, AsyncStorage persistence, offline delivery, and tracking behavior. - [iOS SDK runtime and interaction mechanics](./ios-sdk-runtime-and-interaction-mechanics.md) - iOS diff --git a/documentation/concepts/core-state-management.md b/documentation/concepts/core-state-management.md index 58ce1f604..a68fcde7e 100644 --- a/documentation/concepts/core-state-management.md +++ b/documentation/concepts/core-state-management.md @@ -576,13 +576,14 @@ provider-owned place to subscribe to `eventStream`, `blockedEventStream`, or oth soon as SDK state is available and before child router, screen, or entry effects run. Each subscription still immediately emits its current snapshot. -Both framework roots set up provider-owned SDK instances outside render and render no children while -SDK initialization or provider-managed state subscriber setup is pending. React Web uses -layout-effect scheduling for provider-owned browser SDK creation so ready children normally mount -before first visible paint. React Native keeps async effect scheduling because SDK creation depends -on platform storage and device state. When a framework adapter injects an already-created SDK, -children can render immediately unless `onStatesReady` is provided. React Web also holds children -when `serverOptimizationState` is provided so hydration happens first. +Both framework roots set up provider-owned SDK instances outside render and expose provider-owned +state only after the relevant runtime setup path has started. React Web creates a snapshot runtime +for the first render, then hydrates or creates the live browser SDK in a layout effect so ready +children normally mount before first visible paint. React Native keeps async effect scheduling +because SDK creation depends on platform storage and device state. When a framework adapter injects +an already-created SDK, children can render immediately unless `onStatesReady` is provided. React +Web seeds the initial snapshot from `handoff` when server/static/edge handoff data is provided, so +children can render from that state before the live SDK takes over. ## Diagnostics 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 2f36e5d42..52297e87a 100644 --- a/documentation/concepts/interaction-tracking-in-node-and-stateless-environments.md +++ b/documentation/concepts/interaction-tracking-in-node-and-stateless-environments.md @@ -75,13 +75,13 @@ Apply these constraints before choosing server-only, hybrid, or manual tracking: views without entry views. - Browser Insights delivery needs a current Web SDK profile. In direct Web SDK initialization, the profile can come from `defaults.profile`. In React Web provider handoff and manual Next.js - provider or root setup, pass server-returned Optimization data through `serverOptimizationState`. - In the preferred App Router bound setup, the server root or provider from - `createNextjsAppRouterOptimization()` hands off server data for the browser side automatically. In - Pages Router, pass the server data returned by `getServerSideOptimizationProps()` through - `serverOptimizationState` in `pages/_app.tsx`. The profile can also come from browser-persisted - profile state that persistence consent allows the SDK to load, or a browser Experience API call - such as `page()`, `identify()`, `track()`, or sticky `trackView()`. + provider or root setup, pass server/static/edge Optimization state through the `handoff` prop. In + App Router bound setup, pass the `handoff` returned by `createRequestHandoff()` or + `createHandoffFromSelections()` to the bound root or provider. In Pages Router, pass + `pageProps.contentfulOptimization.handoff` through the bound root or provider in `pages/_app.tsx`. + The profile can also come from browser-persisted profile state that persistence consent allows the + SDK to load, or a browser Experience API call such as `page()`, `identify()`, `track()`, or sticky + `trackView()`. - Browser storage is best-effort. The Web SDK uses `localStorage` and the `ctfl-opt-aid` cookie when persistence consent permits continuity; if storage fails or is unavailable, continuity is limited to in-memory state. @@ -406,10 +406,10 @@ delivery. Choose one of these patterns before enabling interaction tracking: - **Bootstrap the server profile.** For direct Web SDK initialization, serialize the `profile` returned by the server's `page()` or `identify()` call and pass it as `defaults.profile`. For - React Web and manual Next.js provider or root setup, pass the server `OptimizationData` through - `serverOptimizationState`. Preferred Next.js bound roots and providers hand off server data - automatically. Use this when the same server response already rendered personalized HTML from that - profile. + React Web and manual Next.js provider or root setup, pass a browser `handoff` built from the server + data through the root or provider. Next.js bound roots and providers receive request or selection + handoffs from their framework helper. Use this when the same server response already rendered + personalized HTML from that profile. - **Re-evaluate in the browser.** Persist `ctfl-opt-aid` on the server, initialize the Web SDK in the browser, call `page()` after your consent policy allows it, then enable tracking after the page response populates browser profile state. @@ -419,10 +419,10 @@ delivery. Choose one of these patterns before enabling interaction tracking: 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 already emitted that page event. If that -skip leaves the browser with neither a bound server root or provider handoff nor manual -`serverOptimizationState`, 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. +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. 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/locale-handling-in-the-optimization-sdk-suite.md b/documentation/concepts/locale-handling-in-the-optimization-sdk-suite.md index eb0702e91..57c375f92 100644 --- a/documentation/concepts/locale-handling-in-the-optimization-sdk-suite.md +++ b/documentation/concepts/locale-handling-in-the-optimization-sdk-suite.md @@ -39,15 +39,15 @@ package setup, use the relevant integration guide or package README. Each runtime exposes the SDK Experience/event locale through its own API surface: -| Runtime | Locale API surface | -| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Web** | `new ContentfulOptimization({ locale })`, `optimization.locale`, `optimization.states.locale`, `optimization.setLocale(locale)`, and `` | -| **React Web** | ``, provider-owned ``, and `useOptimization()` access to `sdk.locale`, `sdk.states.locale`, and `sdk.setLocale(locale)` | -| **Next.js** | Preferred App Router `createNextjsAppRouterOptimization({ locale })` bound components, Pages Router `getServerSideOptimizationProps(context)`, lower-level server `createNextjsOptimization({ locale })`, request-scoped `getNextjsServerOptimizationData(sdk, { locale })`, ESR `getNextjsEsrOptimizationData(sdk, { locale })`, and manual client `OptimizationRoot locale` | -| **Node** | `new ContentfulOptimization({ locale })` for a default, `optimization.forRequest({ locale })` for request scope, and `experienceOptions.locale` as an advanced pass-through when request `locale` is absent | -| **React Native** | ``, provider-owned ``, `ContentfulOptimization.create({ locale })`, `sdk.locale`, and `sdk.setLocale(locale)` | -| **iOS** | `OptimizationConfig(locale:)`, `OptimizationRoot(config:)`, `OptimizationClient.locale`, and `OptimizationClient.setLocale(_:)` | -| **Android** | `OptimizationConfig(locale = ...)`, Compose `OptimizationRoot(config = ...)`, XML Views `OptimizationManager.initialize(config = ...)`, `OptimizationClient.locale`, and `OptimizationClient.setLocale(locale)` | +| Runtime | Locale API surface | +| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Web** | `new ContentfulOptimization({ locale })`, `optimization.locale`, `optimization.states.locale`, `optimization.setLocale(locale)`, and `` | +| **React Web** | ``, provider-owned ``, and `useOptimization()` access to `sdk.locale`, `sdk.states.locale`, and `sdk.setLocale(locale)` | +| **Next.js** | App Router `createNextjsAppRouterOptimization({ locale })` bound components and `createRequestHandoff({ locale })`; Pages Router `/pages-router/server` `createRequestHandoff(context, { locale })`; lower-level server `createNextjsOptimization({ locale })` and `getNextjsServerOptimizationData(sdk, { locale })`; edge `createEdgeRequestHandoff({ locale })`; and manual client `OptimizationRoot locale` | +| **Node** | `new ContentfulOptimization({ locale })` for a default, `optimization.forRequest({ locale })` for request scope, and `experienceOptions.locale` as an advanced pass-through when request `locale` is absent | +| **React Native** | ``, provider-owned ``, `ContentfulOptimization.create({ locale })`, `sdk.locale`, and `sdk.setLocale(locale)` | +| **iOS** | `OptimizationConfig(locale:)`, `OptimizationRoot(config:)`, `OptimizationClient.locale`, and `OptimizationClient.setLocale(_:)` | +| **Android** | `OptimizationConfig(locale = ...)`, Compose `OptimizationRoot(config = ...)`, XML Views `OptimizationManager.initialize(config = ...)`, `OptimizationClient.locale`, and `OptimizationClient.setLocale(locale)` | ## The locale channels @@ -195,10 +195,7 @@ export const { NextAppAutoPageTracker, OptimizationRoot, OptimizedEntry } = createNextjsAppRouterOptimization({ clientId, locale: appLocale, - server: { - enabled: true, - consent, - }, + consent: { server: consent }, }) ``` @@ -208,10 +205,10 @@ configuration. Keep lower-level/manual surfaces for direct request control. `createNextjsOptimization({ locale })` sets the server SDK default locale, `getNextjsServerOptimizationData(sdk, { locale })` binds an App -Router Server Component request, `getServerSideOptimizationProps(context)` binds a Pages Router -`getServerSideProps` request with explicit `locale` taking precedence over `context.locale`, -`getNextjsEsrOptimizationData(sdk, { locale })` binds a request-rendered ESR flow, and manual client -`OptimizationRoot locale` follows the React Web behavior. +Router Server Component request, the Pages Router server factory's `createRequestHandoff(context, { +locale })` binds a Pages Router `getServerSideProps` request with explicit `locale` taking +precedence over `context.locale`, `createEdgeRequestHandoff({ locale })` binds an edge request, and +manual client `OptimizationRoot locale` follows the React Web behavior. Next.js manual server runtime (TypeScript): @@ -237,12 +234,13 @@ not the current request locale. Manual Server Components pass `headers()` to `getNextjsServerOptimizationData()` so the SDK can derive page context from the request URL captured by the Next.js proxy or middleware helper. -Locale handoff is separate from server optimization state handoff. When the browser provider has the -server data at its boundary, pass it with `serverOptimizationState` on `OptimizationRoot`. With App -Router bound components, the bound server root handles that state handoff. With Pages Router, -`pages/_app.tsx` receives the helper result through `pageProps` and passes it to the bound root. -Keep `defaults` for configuration or default state such as consent policy, not for server-returned -profile, selected optimizations, or changes. +Locale selection is separate from browser state handoff. When the browser provider has the +server/static/edge data at its boundary, pass it with the `handoff` prop on `OptimizationRoot`. With +App Router bound components, pass the `handoff` returned by `createRequestHandoff()` or +`createHandoffFromSelections()` to the bound root. With Pages Router, `pages/_app.tsx` receives +`pageProps.contentfulOptimization.handoff` and passes it to the bound root. Keep `defaults` for +configuration or default state such as consent policy, not for server-returned profile, selected +optimizations, or changes. ## Node and stateless SDKs @@ -321,10 +319,10 @@ Applications own: - Passing the Contentful locale to manual CDA and CPA requests or SDK-managed `contentful.js` fetching. - Passing the SDK Experience/event locale through top-level SDK `locale`, provider `locale`, Next.js - `createNextjsAppRouterOptimization({ locale })`, Pages Router `getServerSideOptimizationProps()`, - lower-level Next.js `getNextjsServerOptimizationData({ locale })`, Next.js ESR - `getNextjsEsrOptimizationData({ locale })`, native config `locale`, native `setLocale`, or Node - `forRequest({ locale })`. + `createNextjsAppRouterOptimization({ locale })`, App Router and Pages Router + `createRequestHandoff({ locale })`, lower-level Next.js + `getNextjsServerOptimizationData({ locale })`, edge `createEdgeRequestHandoff({ locale })`, + native config `locale`, native `setLocale`, or Node `forRequest({ locale })`. - Keeping localized content cache keys distinct. - Refetching Contentful content after locale changes. - Ensuring the Contentful locale is supported by the target Contentful environment. diff --git a/documentation/concepts/optimization-handoff-and-cache-safe-rendering.md b/documentation/concepts/optimization-handoff-and-cache-safe-rendering.md new file mode 100644 index 000000000..0aba5508f --- /dev/null +++ b/documentation/concepts/optimization-handoff-and-cache-safe-rendering.md @@ -0,0 +1,185 @@ +--- +title: Optimization handoff and cache-safe rendering +--- + +# Optimization handoff and cache-safe rendering + +Use this document to understand how server, static, and edge-rendered Optimization state reaches the +browser without putting visitor-specific profile state into public caches. It applies to the +Next.js SDK, React Web SDK, and Web SDK surfaces that consume an Optimization handoff. + +For setup steps, use the relevant Next.js integration guide. This concept explains the mechanics +behind those guides: what a handoff contains, who owns cacheable permutations, how hydration differs +from live updates, and how analytics-only markup can still carry Optimization tracking metadata. + +
+ Table of Contents + + +- [Runtime support](#runtime-support) +- [Inputs and constraints](#inputs-and-constraints) +- [Mental model](#mental-model) +- [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) +- [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) + + +
+ +## Runtime support + +| Runtime surface | Handoff role | +| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| `@contentful/optimization-nextjs/app-router` | Creates request handoffs, customer-owned selection handoffs, bound roots, analytics roots, and server tracking attributes for App Router routes. | +| `@contentful/optimization-nextjs/pages-router` and `/pages-router/server` | Creates `getServerSideProps` request handoffs and customer-owned selection handoffs for Pages Router routes. | +| `@contentful/optimization-nextjs/edge` | Creates Edge runtime request handoffs and customer-owned selection handoffs. | +| `@contentful/optimization-react-web` | Consumes content handoffs in `OptimizationRoot` and analytics-only handoffs in `OptimizationAnalyticsRoot`. | +| `@contentful/optimization-web` | Hydrates content handoffs into a live browser SDK and analytics-only handoffs into a narrow analytics runtime. | + +## Inputs and constraints + +An `OptimizationHandoff` is the framework-neutral shape for state produced before browser hydration. +It can contain: + +- `state.selectedOptimizations` - the selected experience and variant records used for entry + resolution. +- `state.changes` - Custom Flag changes derived from the selected optimizations. +- `state.profile` - profile state from a request-backed Experience API response. +- `entries` - managed-entry baseline snapshots that let browser-managed entries hydrate from the + same baseline entry the server or static render used. +- `cache` - metadata that describes where the rendered output is allowed to be cached. + +Browser handoffs add two fields: + +- `hydration` - the browser presentation policy for already-rendered content. +- `initialPageEvent` - whether the browser emits or skips the first page event for this route. + +The SDK serializes and hydrates the state it receives. It does not infer application segments, +campaigns, markets, or other public permutations from the URL. When a handoff is cacheable across +visitors, application code supplies the selected optimizations and cache key. + +## Mental model + +Handoff is a render boundary. A server, build, ISR, or edge path resolves content using a known +Optimization state, then passes that same state to the browser so the first browser render matches +the markup. + +```text +request, build, ISR, or edge code chooses Optimization state + -> route resolves entries or renders analytics-only markup + -> route creates a browser handoff with cache metadata + -> browser root hydrates from the handoff + -> browser SDK takes over according to hydration and liveUpdates policy +``` + +The handoff is not a cache key by itself. Cache safety comes from matching the rendered output, the +handoff state, and the cache scope. + +## Cache scopes + +| Scope | Use it for | Cache rule | +| -------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | +| `private-request` | A real request backed by cookies, request headers, profile continuity, or an Experience API response for one visitor. | Keep the rendered output out of shared public caches. | +| `public-permutation` | A customer-owned segment, market, campaign, or path where application code already chose the selected optimizations. | Provide an application-owned `cache.key` that covers the full public permutation. | +| `static` | Build-time or baseline output that does not depend on request profile state. | Do not include profile state. | + +The cache-safety helper reports warnings rather than blocking rendering. It warns when profile state +appears in a `public-permutation` or `static` handoff, and when a `public-permutation` handoff lacks +an application-owned key. + +## Customer-owned permutations + +A customer-owned public permutation is a cacheable output your application can name from a finite +app-owned or customer-owned registry without reading a visitor profile. The registry can come from a +segment service, CMS or config mapping, static artifact, or reviewed app config. + +The Experience API evaluates one request context. It is not a registry source for public +permutations and does not enumerate the public segments, markets, campaigns, routes, or locales your +application can cache. + +A registry record is valid for a static, ISR, or edge public output only when it has enough +dimensions to fetch, resolve, hand off, and cache one output: public key or slug, locale, baseline +entry IDs, `selectedOptimizations`, optional `changes`, and an app-owned revision or cache version. +The SDK serializes and hydrates the state you supply. It does not discover or enumerate valid public +permutations for the application. + +The validity boundary is cache ownership. Public and static handoffs must not use request profile, +cookies, headers, or request-specific Experience API output. Public cache keys must cover the public +permutation dimensions plus locale, baseline entry IDs, selected optimizations, and the app revision, +environment, or cache version when those values can change the rendered output. + +For selected-optimization shape, content model, variant-index, and fallback details, see +[Entry optimization and variant resolution](./entry-personalization-and-variant-resolution.md). For +the procedural Next.js recipe, see +[Rendering personalized Next.js routes with static, ISR, and edge handoffs](../guides/rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.md). + +## Hydration and live updates + +`hydration` controls the first browser presentation over already-rendered markup. + +| Hydration mode | Effect | +| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| `preserve-server` | Keep server, static, ISR, or edge-rendered content visible while the browser hydrates. | +| `client-only-hidden-until-ready` | Let the browser own content resolution; the default loading presentation can hide the baseline layout target until resolution settles or times out. | +| `analytics-only` | Hydrate analytics state without providing content resolution context. | + +`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 +content must react to consent, identity, profile, or preview changes after hydration. Preview state +can force live re-resolution for authoring flows. + +## 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. Selection handoff helpers +require application code to supply it because customer-owned static and public permutations do not +emit a server request event by themselves. + +React Web roots can emit or skip the handoff-owned initial page event when they receive both +`routeKey` and `buildPagePayload`. Next.js route trackers use the same `"emit"` or `"skip"` control +for the first browser route and then track later navigations. + +## Analytics-only handoff and tracking attributes + +Some server, static, ISR, or edge routes render the final HTML themselves and use the browser SDK +only for page and interaction tracking. Those routes use an analytics-only handoff: + +- `OptimizationAnalyticsRoot` hydrates an analytics runtime, not a content resolution provider. +- `getServerTrackingAttributes(baselineEntry, resolvedData)` attaches the SDK-owned `data-ctfl-*` + attributes that browser entry-interaction tracking consumes. +- The `data-ctfl-*` attributes describe the resolved entry, baseline entry, optimization context, + variant index, sticky selection, and clickable state. + +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 +markup remains private to the request. + +## Why profile state stays out of public caches + +Profile state is visitor-specific. Request-backed selected optimizations, Custom Flag changes, merge +tag values, and rendered personalized HTML can all depend on that profile. If that state enters a +shared public cache, another visitor can receive the wrong variant, wrong Custom Flag state, wrong +merge-tag output, or a page-event handoff that was created for a different profile. + +Use request-backed handoffs for private request rendering. Use customer-owned selection handoffs for +cacheable public permutations. Cache raw Contentful baseline entries according to your application +policy, but keep resolved personalized output scoped to the state that produced it. + +## Related documentation + +- [Rendering personalized Next.js routes with static, ISR, and edge handoffs](../guides/rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.md) +- [Integrating the Optimization Next.js SDK in a Next.js App Router app](../guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md) +- [Integrating the Optimization Next.js SDK in a Next.js Pages Router app](../guides/integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md) +- [Entry optimization and variant resolution](./entry-personalization-and-variant-resolution.md) +- [Profile synchronization between client and server](./profile-synchronization-between-client-and-server.md) +- [Interaction tracking in Web SDKs](./interaction-tracking-in-web-sdks.md) diff --git a/documentation/concepts/profile-synchronization-between-client-and-server.md b/documentation/concepts/profile-synchronization-between-client-and-server.md index dfc217cc3..d2d6ace75 100644 --- a/documentation/concepts/profile-synchronization-between-client-and-server.md +++ b/documentation/concepts/profile-synchronization-between-client-and-server.md @@ -43,13 +43,13 @@ state, and which application boundaries still belong to your implementation. Profile synchronization applies differently across SDK runtimes: -| Runtime | Applicability | -| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Node SDK** | Stateless server runtime. It uses `forRequest()` to bind consent, locale, event context, and the current profile ID for one request. It does not store cookies or long-lived profile state. | -| **Web SDK** | Stateful browser runtime. It can use localStorage and the readable `ctfl-opt-aid` cookie for durable browser-server continuity when persistence consent resolves to `true`. | -| **React Web SDK** | React wrapper around the Web SDK. Providers and hooks share the same browser state, localStorage, cookie, and persistence-consent mechanics as the Web SDK. | -| **Next.js adapter** | Hybrid runtime. Server helpers bind request-scoped Node clients. App Router proxy, Pages Router server helpers, and ESR persistence own `ctfl-opt-aid` writes and clears. Client components continue through the Web or React Web SDK state. | -| **React Native and mobile SDKs** | React Native uses AsyncStorage, and native mobile SDKs use platform storage instead of browser cookies. They can continue mobile profiles through the Experience API, but they do not provide a built-in `ctfl-opt-aid` cookie handoff to a Node server. | +| Runtime | Applicability | +| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Node SDK** | Stateless server runtime. It uses `forRequest()` to bind consent, locale, event context, and the current profile ID for one request. It does not store cookies or long-lived profile state. | +| **Web SDK** | Stateful browser runtime. It can use localStorage and the readable `ctfl-opt-aid` cookie for durable browser-server continuity when persistence consent resolves to `true`. | +| **React Web SDK** | React wrapper around the Web SDK. Providers and hooks share the same browser state, localStorage, cookie, and persistence-consent mechanics as the Web SDK. | +| **Next.js adapter** | Hybrid runtime. App Router, Pages Router, manual server, and edge helpers bind request-scoped SDK work and return explicit browser `handoff` objects. Pages Router, edge, and lower-level response-persistence helpers write or clear `ctfl-opt-aid`. Client components continue through the Web or React Web SDK state. | +| **React Native and mobile SDKs** | React Native uses AsyncStorage, and native mobile SDKs use platform storage instead of browser cookies. They can continue mobile profiles through the Experience API, but they do not provide a built-in `ctfl-opt-aid` cookie handoff to a Node server. | ## The synchronization model @@ -104,12 +104,12 @@ profile-changing events: `defaults.changes`, and consent defaults bootstrap one runtime. They do not synchronize server and client state unless both runtimes also use the same profile ID or the same Experience API response. In React Web provider handoff and manual Next.js provider or root handoff, keep - `defaults` for configuration or default state such as consent policy, and pass server-returned - Optimization data through `serverOptimizationState` when the provider or root receives the data - directly. In App Router bound components from `createNextjsAppRouterOptimization()`, the bound - server root or provider owns that handoff. In Pages Router, pass - `props.contentfulOptimization.serverOptimizationState` from `getServerSideOptimizationProps()` - into the bound root or provider in `pages/_app.tsx`. + `defaults` for configuration or default state such as consent policy, and pass server/static/edge + browser state through the `handoff` prop when the provider or root receives that state directly. + In App Router bound components from `createNextjsAppRouterOptimization()`, pass the `handoff` + returned by `createRequestHandoff()` or `createHandoffFromSelections()` to the bound root or + provider. In Pages Router, pass `pageProps.contentfulOptimization.handoff` from + `getServerSideProps` into the bound root or provider in `pages/_app.tsx`. For consent gates, see [Consent management in the Optimization SDK Suite](./consent-management-in-the-optimization-sdk-suite.md). @@ -137,17 +137,17 @@ built-in cookie handoff between a React Native app and a Node server. ## Server-rendered profile data Choose the server-rendering path before implementing a hybrid flow, because the choice determines -whether the browser needs only the shared profile ID or a bootstrapped `OptimizationData` snapshot. +whether the browser needs only the shared profile ID or bootstrapped browser state. The shared cookie is enough when the browser performs personalization after hydration. It is not enough when the server already rendered profile-derived HTML and the browser must continue from the same evaluated data before its first client-side Experience response. -| Path | Use when | Browser startup contract | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Server owns the first render** | The server renders selected variants and profile-derived values, and the client can wait for fresh SDK data before re-resolving. | Persist `ctfl-opt-aid` when allowed, and prevent stale browser caches from driving visible personalized content before a later Experience response. | -| **Server bootstraps the browser** | The client must continue from the same evaluated data before its first browser Experience response. | For direct Web SDK initialization, serialize the server's `profile`, `selectedOptimizations`, and `changes` into `defaults`. For App Router bound components, the bound server root or provider hands off the server `OptimizationData`. For Pages Router, pass the server `OptimizationData` from `pageProps` through `serverOptimizationState`. For React Web and manual Next.js provider or root handoff, pass the server `OptimizationData` through `serverOptimizationState`. | -| **Browser owns personalization** | The server can render baseline or loading output while the client resolves personalization after hydration. | Persist `ctfl-opt-aid` when allowed, then let the Web SDK call `page()` and resolve entries in the app layer, or use SDK-managed fetching only when configured with a `contentful.js` client, after selected optimizations are available. | +| Path | Use when | Browser startup contract | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Server owns the first render** | The server renders selected variants and profile-derived values, and the client can wait for fresh SDK data before re-resolving. | Persist `ctfl-opt-aid` when allowed, and prevent stale browser caches from driving visible personalized content before a later Experience response. | +| **Server bootstraps the browser** | The client must continue from the same evaluated data before its first browser Experience response. | For direct Web SDK initialization, serialize the server's `profile`, `selectedOptimizations`, and `changes` into `defaults`. For App Router bound components, pass the browser `handoff` returned by `createRequestHandoff()` or `createHandoffFromSelections()` to the bound server root or provider. For Pages Router, pass `pageProps.contentfulOptimization.handoff` through the bound root or provider in `_app.tsx`. For React Web and manual Next.js provider or root handoff, pass a browser `handoff` built from the server/static/edge state through the root or provider. | +| **Browser owns personalization** | The server can render baseline or loading output while the client resolves personalization after hydration. | Persist `ctfl-opt-aid` when allowed, then let the Web SDK call `page()` and resolve entries in the app layer, or use SDK-managed fetching only when configured with a `contentful.js` client, after selected optimizations are available. | Direct Web SDK bootstrapping must use the same `OptimizationData` response that drove the server render: @@ -166,12 +166,12 @@ const optimization = new ContentfulOptimization({ If the browser re-resolves entries from stale localStorage while the server rendered from a newer profile evaluation, the user can see a mismatched variant or profile-derived value. For direct Web -SDK initialization, use explicit defaults. App Router bound components hand off server -`OptimizationData` through the bound server root or provider. Pages Router bound components receive -the same data from `pageProps` in `pages/_app.tsx`. For React Web and manual Next.js provider or -root handoff, pass the server `OptimizationData` to `serverOptimizationState` when the provider or -root receives the data. A fresh client-side `page()` response or a render boundary can also prevent -stale cached state from driving visible content. +SDK initialization, use explicit defaults. App Router bound components receive browser handoff data +through the `handoff` returned by `createRequestHandoff()` or `createHandoffFromSelections()`. Pages +Router bound components receive `pageProps.contentfulOptimization.handoff` in `pages/_app.tsx`. For +React Web and manual Next.js provider or root handoff, pass a browser `handoff` built from the +server/static/edge state to the provider or root. A fresh client-side `page()` response or a render +boundary can also prevent stale cached state from driving visible content. Personalized HTML is not shared-cache safe unless the cache varies on all personalization inputs. Raw Contentful entries are the safer cache boundary; resolve variants per request or per profile @@ -499,17 +499,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 App Router bound components, let the bound server root or provider hand off server data. For Pages Router, pass `pageProps` data through `serverOptimizationState`. For React Web and manual Next.js provider or root handoff, use `serverOptimizationState`. | -| 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 App Router bound components, pass the request or selection `handoff` to the bound root or provider. 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. | ## Implementation checklist @@ -527,10 +527,10 @@ Use this checklist when implementing a hybrid Node and browser profile flow: - Confirm persistence consent resolves to `true` before expecting the Web SDK to load persisted profile-continuity state or adopt `ctfl-opt-aid`. - Render from the `OptimizationData` response that matches the current identity state. -- Bootstrap direct Web SDK `defaults`, use App Router bound component handoff, pass Pages Router - `pageProps` data through `serverOptimizationState`, or pass `serverOptimizationState` through - React Web or manual Next.js provider or root handoff when server-rendered personalized output must - match client-side resolution before the first browser Experience response. +- Bootstrap direct Web SDK `defaults`, pass an App Router request or selection `handoff`, pass + Pages Router `pageProps.contentfulOptimization.handoff`, or pass a browser `handoff` through React + Web or manual Next.js provider or root handoff when server-rendered personalized output must match + client-side resolution before the first browser Experience response. - Clear both browser state and server persistence when consent revocation must end profile continuity. - Use single-locale CDA entry payloads for both manual `baselineEntry` resolution and SDK-managed diff --git a/documentation/guides/README.md b/documentation/guides/README.md index d1c90daac..ba768bcab 100644 --- a/documentation/guides/README.md +++ b/documentation/guides/README.md @@ -13,6 +13,7 @@ children: - ./integrating-the-optimization-android-sdk-in-a-compose-app.md - ./integrating-the-optimization-android-sdk-in-a-views-app.md - ./building-a-custom-javascript-optimization-adapter.md + - ./rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.md - ./forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md --- @@ -32,13 +33,13 @@ Server and web SDK guides are listed before native and mobile SDK guides. ### Server and web SDKs -| Guide | Runtime or app type | Package | -| ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------ | -| [Node SDK](./integrating-the-node-sdk-in-a-node-app.md) | Node server, custom SSR server, or server-side function | `@contentful/optimization-node` | -| [Web SDK](./integrating-the-web-sdk-in-a-web-app.md) | Browser app, static site, multi-page app, SPA, Angular app, or custom frontend runtime | `@contentful/optimization-web` | -| [React Web SDK](./integrating-the-react-web-sdk-in-a-react-app.md) | React browser app | `@contentful/optimization-react-web` | -| [Next.js SDK in App Router](./integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md) | Next.js App Router app with server-personalized first paint and browser re-resolution after hydration | `@contentful/optimization-nextjs` | -| [Next.js SDK in Pages Router](./integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md) | Next.js Pages Router app with `getServerSideProps` personalization and state handoff | `@contentful/optimization-nextjs` | +| Guide | Runtime or app type | Package | +| ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------ | +| [Node SDK](./integrating-the-node-sdk-in-a-node-app.md) | Node server, custom SSR server, or server-side function | `@contentful/optimization-node` | +| [Web SDK](./integrating-the-web-sdk-in-a-web-app.md) | Browser app, static site, multi-page app, SPA, Angular app, or custom frontend runtime | `@contentful/optimization-web` | +| [React Web SDK](./integrating-the-react-web-sdk-in-a-react-app.md) | React browser app | `@contentful/optimization-react-web` | +| [Next.js SDK in App Router](./integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md) | Next.js App Router app with request, selection, edge, and analytics-only handoff paths | `@contentful/optimization-nextjs` | +| [Next.js SDK in Pages Router](./integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md) | Next.js Pages Router app with `getServerSideProps` request handoff | `@contentful/optimization-nextjs` | ### Native and mobile SDKs @@ -57,6 +58,9 @@ Native and mobile SDK guides route to beta SDK surfaces. - [Building a custom JavaScript Optimization adapter](./building-a-custom-javascript-optimization-adapter.md) - Build a low-level adapter only when no official SDK package fits your JavaScript runtime or framework. +- [Rendering personalized Next.js routes with static, ISR, and edge handoffs](./rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.md) - + Choose cache-safe handoff patterns for Next.js static, ISR, edge, and analytics-only routes after + integrating the Next.js SDK. - [Forwarding Optimization SDK context to analytics and tag-management tools](./forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md) - Forward optimization context to analytics, tag-management, customer-data, or product-analytics tools after SDK integration. diff --git a/documentation/guides/choosing-the-right-sdk.md b/documentation/guides/choosing-the-right-sdk.md index 10f84cdd5..d14aeceff 100644 --- a/documentation/guides/choosing-the-right-sdk.md +++ b/documentation/guides/choosing-the-right-sdk.md @@ -24,15 +24,21 @@ tooling, tests, or first-party integrations that need shared SDK primitives or r For mixed server and browser applications, use the adapter when one exists. Next.js App Router apps use `@contentful/optimization-nextjs/app-router`; it provides `createNextjsAppRouterOptimization()`, -an automatic factory that returns app-local bound `OptimizationRoot`, `OptimizationProvider`, -`OptimizedEntry`, and route trackers for Server and Client Components. Next.js Pages Router apps use -`@contentful/optimization-nextjs/pages-router` plus -`@contentful/optimization-nextjs/pages-router/server` for `getServerSideProps`. The Next.js package -root is intentionally not an import path; use `/client`, `/server`, `/request-handler`, `/esr`, and -`/tracking-attributes` subpaths for lower-level control. Non-Next.js server-rendered apps can +an automatic factory that returns app-local bound roots, `OptimizedEntry`, route trackers, request +handoff helpers, selection handoff helpers, and tracking helpers for Server and Client Components. +Next.js Pages Router apps use `@contentful/optimization-nextjs/pages-router` plus +`@contentful/optimization-nextjs/pages-router/server` for `getServerSideProps` request handoff. The +Next.js package root is intentionally not an import path; use `/client`, `/server`, +`/request-handler`, `/edge`, and `/tracking-attributes` subpaths for lower-level control. +Non-Next.js server-rendered apps can combine `@contentful/optimization-node` on the server with `@contentful/optimization-web` or `@contentful/optimization-react-web` in the browser. +After choosing App Router or Pages Router, use +[Rendering personalized Next.js routes with static, ISR, and edge handoffs](./rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.md) +when individual Next.js routes need static generation, ISR, edge rendering, or analytics-only +handoff patterns. + Angular, Vue, Svelte, Web Components, and custom browser framework apps use `@contentful/optimization-web`. Nest.js and other Node server frameworks use `@contentful/optimization-node` unless the app is a Next.js App Router or Pages Router app covered @@ -64,19 +70,19 @@ platform-native apps that can accept beta native API and setup changes. Use this table to choose the primary package and the next integration guide: -| Reader need | Choose | Why | Next guide | -| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -| Nest.js app, Node server, server function, or SSR layer outside the Next.js adapter | `@contentful/optimization-node` | It provides stateless, request-scoped profile evaluation, event emission, managed Contentful entry fetching and prefetching, entry resolution, and caching guidance for Node runtimes. | [Integrating the Optimization Node SDK in a Node app](./integrating-the-node-sdk-in-a-node-app.md) | -| Angular, Vue, Svelte, Web Components, non-React browser app, or custom browser framework app | `@contentful/optimization-web` | It owns browser consent state, anonymous ID persistence, managed Contentful entry fetching and prefetching, automatic entry interaction tracking, browser event delivery, and Web Components. | [Integrating the Optimization Web SDK in a web app](./integrating-the-web-sdk-in-a-web-app.md) | -| React browser app outside Next.js integration | `@contentful/optimization-react-web` | It wraps the Web SDK with React providers, hooks, router page tracking, optimized entry rendering by entry ID, interaction tracking, and live update semantics. | [Integrating the Optimization React Web SDK in a React app](./integrating-the-react-web-sdk-in-a-react-app.md) | -| Next.js App Router app with server-personalized first paint and browser re-resolution after hydration | `@contentful/optimization-nextjs/app-router` | Its `/app-router` bound `OptimizationRoot`, `OptimizedEntry`, and route tracker keep personalized initial HTML before the browser SDK owns reactive entry resolution, live updates, route events, and preview-panel attachment. | [Integrating the Optimization Next.js SDK in a Next.js App Router app](./integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md) | -| Next.js Pages Router app with `getServerSideProps` personalization | `@contentful/optimization-nextjs/pages-router` plus `/pages-router/server` | Its `/pages-router` components and `/pages-router/server` helper pass server Optimization state through `pageProps` and avoid duplicate initial page events. | [Integrating the Optimization Next.js SDK in a Next.js Pages Router app](./integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md) | -| Custom JavaScript runtime or framework adapter where no official SDK fits | `@contentful/optimization-core` plus `@contentful/optimization-core/entry-source` | Core provides shared state and resolution primitives. The entry-source subpath manages baseline-entry or entry-ID source lifecycle while the adapter owns rendering, tracking, and runtime policy. | [Building a custom JavaScript Optimization adapter](./building-a-custom-javascript-optimization-adapter.md) | -| React Native app | `@contentful/optimization-react-native` | It provides a stateful JavaScript mobile runtime with React providers, hooks, `OptimizedEntry`, screen tracking, optional offline-aware delivery, and preview-panel support. | [Integrating the Optimization React Native SDK in a React Native app](./integrating-the-react-native-sdk-in-a-react-native-app.md) | -| Native iOS app built with SwiftUI that accepts beta native API and setup changes | `ContentfulOptimization` Swift Package | It provides native Swift APIs, SwiftUI helpers, persistence, networking, lifecycle handling, screen tracking, entry rendering, and preview-panel UI. | [Integrating the Optimization iOS SDK in a SwiftUI app](./integrating-the-optimization-ios-sdk-in-a-swiftui-app.md) | -| Native iOS app built with UIKit or direct client ownership that accepts beta native API and setup changes | `ContentfulOptimization` Swift Package | It exposes the same native iOS runtime through direct client APIs and UIKit-compatible preview, screen tracking, and entry-rendering patterns. | [Integrating the Optimization iOS SDK in a UIKit app](./integrating-the-optimization-ios-sdk-in-a-uikit-app.md) | -| Native Android app built with Jetpack Compose that accepts beta native API and setup changes | `com.contentful.java:optimization-android` | The Android AAR includes the stateful Kotlin client, Compose UI helpers, screen tracking, entry optimization, preview controls, and offline event delivery. | [Integrating the Optimization Android SDK in a Jetpack Compose app](./integrating-the-optimization-android-sdk-in-a-compose-app.md) | -| Native Android app built with Android Views or XML layouts that accepts beta native API and setup changes | `com.contentful.java:optimization-android` | The same Android AAR includes Android Views helpers such as `OptimizationManager`, `OptimizedEntryView`, `ScreenTracker`, preview controls, and the stateful client. | [Integrating the Optimization Android SDK in an Android Views app](./integrating-the-optimization-android-sdk-in-a-views-app.md) | +| Reader need | Choose | Why | Next guide | +| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| Nest.js app, Node server, server function, or SSR layer outside the Next.js adapter | `@contentful/optimization-node` | It provides stateless, request-scoped profile evaluation, event emission, managed Contentful entry fetching and prefetching, entry resolution, and caching guidance for Node runtimes. | [Integrating the Optimization Node SDK in a Node app](./integrating-the-node-sdk-in-a-node-app.md) | +| Angular, Vue, Svelte, Web Components, non-React browser app, or custom browser framework app | `@contentful/optimization-web` | It owns browser consent state, anonymous ID persistence, managed Contentful entry fetching and prefetching, automatic entry interaction tracking, browser event delivery, and Web Components. | [Integrating the Optimization Web SDK in a web app](./integrating-the-web-sdk-in-a-web-app.md) | +| React browser app outside Next.js integration | `@contentful/optimization-react-web` | It wraps the Web SDK with React providers, hooks, router page tracking, optimized entry rendering by entry ID, interaction tracking, and live update semantics. | [Integrating the Optimization React Web SDK in a React app](./integrating-the-react-web-sdk-in-a-react-app.md) | +| Next.js App Router app with server-personalized first paint and browser re-resolution after hydration | `@contentful/optimization-nextjs/app-router` | Its `/app-router` bound roots, `OptimizedEntry`, route tracker, request handoff, selection handoff, and tracking helpers keep personalized initial HTML aligned with browser takeover. | [Integrating the Optimization Next.js SDK in a Next.js App Router app](./integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md) | +| Next.js Pages Router app with `getServerSideProps` personalization | `@contentful/optimization-nextjs/pages-router` plus `/pages-router/server` | Its `/pages-router` components and `/pages-router/server` request handoff helper pass browser handoff through `pageProps` and avoid duplicate initial page events. | [Integrating the Optimization Next.js SDK in a Next.js Pages Router app](./integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md) | +| Custom JavaScript runtime or framework adapter where no official SDK fits | `@contentful/optimization-core` plus `@contentful/optimization-core/entry-source` | Core provides shared state and resolution primitives. The entry-source subpath manages baseline-entry or entry-ID source lifecycle while the adapter owns rendering, tracking, and runtime policy. | [Building a custom JavaScript Optimization adapter](./building-a-custom-javascript-optimization-adapter.md) | +| React Native app | `@contentful/optimization-react-native` | It provides a stateful JavaScript mobile runtime with React providers, hooks, `OptimizedEntry`, screen tracking, optional offline-aware delivery, and preview-panel support. | [Integrating the Optimization React Native SDK in a React Native app](./integrating-the-react-native-sdk-in-a-react-native-app.md) | +| Native iOS app built with SwiftUI that accepts beta native API and setup changes | `ContentfulOptimization` Swift Package | It provides native Swift APIs, SwiftUI helpers, persistence, networking, lifecycle handling, screen tracking, entry rendering, and preview-panel UI. | [Integrating the Optimization iOS SDK in a SwiftUI app](./integrating-the-optimization-ios-sdk-in-a-swiftui-app.md) | +| Native iOS app built with UIKit or direct client ownership that accepts beta native API and setup changes | `ContentfulOptimization` Swift Package | It exposes the same native iOS runtime through direct client APIs and UIKit-compatible preview, screen tracking, and entry-rendering patterns. | [Integrating the Optimization iOS SDK in a UIKit app](./integrating-the-optimization-ios-sdk-in-a-uikit-app.md) | +| Native Android app built with Jetpack Compose that accepts beta native API and setup changes | `com.contentful.java:optimization-android` | The Android AAR includes the stateful Kotlin client, Compose UI helpers, screen tracking, entry optimization, preview controls, and offline event delivery. | [Integrating the Optimization Android SDK in a Jetpack Compose app](./integrating-the-optimization-android-sdk-in-a-compose-app.md) | +| Native Android app built with Android Views or XML layouts that accepts beta native API and setup changes | `com.contentful.java:optimization-android` | The same Android AAR includes Android Views helpers such as `OptimizationManager`, `OptimizedEntryView`, `ScreenTracker`, preview controls, and the stateful client. | [Integrating the Optimization Android SDK in an Android Views app](./integrating-the-optimization-android-sdk-in-a-views-app.md) | ## Alternatives @@ -104,17 +110,18 @@ Use this table to choose the primary package and the next integration guide: After choosing the package, follow the matching guide: -| Runtime or task | Guide | -| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Node servers and server-side rendering | [Integrating the Optimization Node SDK in a Node app](./integrating-the-node-sdk-in-a-node-app.md) | -| Browser apps without React | [Integrating the Optimization Web SDK in a web app](./integrating-the-web-sdk-in-a-web-app.md) | -| React browser apps | [Integrating the Optimization React Web SDK in a React app](./integrating-the-react-web-sdk-in-a-react-app.md) | -| Next.js App Router apps | [Integrating the Optimization Next.js SDK in a Next.js App Router app](./integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md) | -| Next.js Pages Router apps | [Integrating the Optimization Next.js SDK in a Next.js Pages Router app](./integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md) | -| Custom JavaScript adapter | [Building a custom JavaScript Optimization adapter](./building-a-custom-javascript-optimization-adapter.md) | -| React Native apps | [Integrating the Optimization React Native SDK in a React Native app](./integrating-the-react-native-sdk-in-a-react-native-app.md) | -| iOS SwiftUI apps | [Integrating the Optimization iOS SDK in a SwiftUI app](./integrating-the-optimization-ios-sdk-in-a-swiftui-app.md) | -| iOS UIKit apps | [Integrating the Optimization iOS SDK in a UIKit app](./integrating-the-optimization-ios-sdk-in-a-uikit-app.md) | -| Android Jetpack Compose apps | [Integrating the Optimization Android SDK in a Jetpack Compose app](./integrating-the-optimization-android-sdk-in-a-compose-app.md) | -| Android Views apps | [Integrating the Optimization Android SDK in an Android Views app](./integrating-the-optimization-android-sdk-in-a-views-app.md) | -| Analytics and tag-management forwarding | [Forwarding Optimization SDK context to analytics and tag-management tools](./forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md) | +| Runtime or task | Guide | +| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Node servers and server-side rendering | [Integrating the Optimization Node SDK in a Node app](./integrating-the-node-sdk-in-a-node-app.md) | +| Browser apps without React | [Integrating the Optimization Web SDK in a web app](./integrating-the-web-sdk-in-a-web-app.md) | +| React browser apps | [Integrating the Optimization React Web SDK in a React app](./integrating-the-react-web-sdk-in-a-react-app.md) | +| Next.js App Router apps | [Integrating the Optimization Next.js SDK in a Next.js App Router app](./integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md) | +| Next.js Pages Router apps | [Integrating the Optimization Next.js SDK in a Next.js Pages Router app](./integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md) | +| Next.js static, ISR, edge, and analytics-only routes | [Rendering personalized Next.js routes with static, ISR, and edge handoffs](./rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.md) | +| Custom JavaScript adapter | [Building a custom JavaScript Optimization adapter](./building-a-custom-javascript-optimization-adapter.md) | +| React Native apps | [Integrating the Optimization React Native SDK in a React Native app](./integrating-the-react-native-sdk-in-a-react-native-app.md) | +| iOS SwiftUI apps | [Integrating the Optimization iOS SDK in a SwiftUI app](./integrating-the-optimization-ios-sdk-in-a-swiftui-app.md) | +| iOS UIKit apps | [Integrating the Optimization iOS SDK in a UIKit app](./integrating-the-optimization-ios-sdk-in-a-uikit-app.md) | +| Android Jetpack Compose apps | [Integrating the Optimization Android SDK in a Jetpack Compose app](./integrating-the-optimization-android-sdk-in-a-compose-app.md) | +| Android Views apps | [Integrating the Optimization Android SDK in an Android Views app](./integrating-the-optimization-android-sdk-in-a-views-app.md) | +| Analytics and tag-management forwarding | [Forwarding Optimization SDK context to analytics and tag-management tools](./forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md) | 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 3c30d27a9..583777b91 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 @@ -257,8 +257,7 @@ server/client escape hatches. For Pages Router integrations, configure `onStatesReady` once in `createNextjsPagesRouterOptimization()` from `@contentful/optimization-nextjs/pages-router`, then -pass `pageProps.contentfulOptimization.serverOptimizationState` to the bound root in -`pages/_app.tsx`. +pass `pageProps.contentfulOptimization.handoff` to the bound root in `pages/_app.tsx`. **Adapt this to your use case:** @@ -267,9 +266,9 @@ import { createNextjsAppRouterOptimization } from '@contentful/optimization-next const forwardedMessageIds = new Set() -export const { proxy, NextAppAutoPageTracker, OptimizationRoot, OptimizedEntry } = +export const { NextAppAutoPageTracker, OptimizationRoot, OptimizedEntry } = createNextjsAppRouterOptimization({ - // ...clientId, environment, locale, server, defaults + // ...clientId, environment, locale, consent onStatesReady: (states) => { const initialMessageId = states.eventStream.current?.messageId @@ -321,15 +320,15 @@ Applies when a Node route, server action, middleware/proxy flow, or lower-level/ server flow already called a request-bound SDK method and owns the analytics event for that request. Use the `data` from the same accepted SDK call that rendered the response or handled the server -event. App Router Next.js integrations load request server data through the bound components created -by `createNextjsAppRouterOptimization()`. Use the config-bound `getServerSideOptimizationProps()` -helper for Pages Router `getServerSideProps`; it returns the same `OptimizationData` shape in its -`data` field and serializable `props.contentfulOptimization`. Use +event. App Router integrations use `createRequestHandoff()` from the app-local bound factory when a +route should pass request state to the browser. Pages Router integrations use the config-bound +`createRequestHandoff()` helper from `@contentful/optimization-nextjs/pages-router/server` inside +`getServerSideProps` and return `props.contentfulOptimization.handoff`. Use `getNextjsServerOptimizationData()` only when you intentionally build a lower-level/manual `/server` flow. When the SDK is configured with `contentful: { client }`, prefer the request-bound managed entry helper so the entry decision and analytics context share the same request data. Browser state streams cannot explain a server-rendered first paint unless you intentionally hydrate the browser -with the same Optimization data. +with the same handoff. **Adapt this to your use case:** 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 c279328ce..72751c98a 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 @@ -1,19 +1,17 @@ # Integrating the Optimization Next.js SDK in a Next.js App Router app -Use this guide to add Contentful personalization to a Next.js App Router site you already have. By -the end of the quick start, one piece of content will be personalized per visitor in the -server-rendered HTML — no flash of default content, while your app keeps ownership of fetching and -rendering. +Use this guide to render Contentful entries with personalized server first paint in a Next.js App +Router app, then let the browser continue from the same Optimization handoff. **New to personalization?** Here is the whole idea in four points: -- In Contentful you author **variants** of an entry and attach them to an **experience** — a rule +- In Contentful you author **variants** of an entry and attach them to an **experience** - a rule that decides which visitors see which variant. -- When a page is requested, Contentful's **Experience API** looks at the current visitor and picks - the variant for each experience. Swapping a fetched entry for its picked variant is called +- On each request, Contentful's **Experience API** looks at the request context and picks the + variant for each experience. Swapping a fetched entry for its picked variant is called **resolving** the entry. - Your app hands a Contentful entry to the SDK at the point where that entry becomes output. The SDK - gives back the selected variant, or the original entry when no variant applies—the **baseline + gives back the selected variant, or the original entry when no variant applies - the **baseline fallback**. You can fetch the entry yourself or give the SDK your Contentful client and an entry ID; either way, the client stays yours. - You render the returned entry with the same application components you already use. @@ -23,149 +21,124 @@ them. You will get there in two milestones: -- **Milestone 1 — personalized first paint (the quick start below).** A visitor sees their variant - in the server HTML. This is complete and shippable on its own. -- **Milestone 2 — browser takeover (opt-in, later).** Content re-personalizes live when consent, - identity, or profile changes, without a page reload. See +- **Milestone 1 - Personalized first paint from one server render.** The quick start below is + shippable when your policy allows server personalization. +- **Milestone 2 - Browser takeover and live updates.** See [Browser takeover and live updates](#browser-takeover-and-live-updates). -This guide uses `@contentful/optimization-nextjs`. The `/app-router` factory gives you app-local -components that do the right thing in both Server and Client Components. Your app keeps ownership of -Contentful fetching, consent policy, identity, routing, caching, and rendering. - -If your app uses the Pages Router, use the +This guide uses `@contentful/optimization-nextjs/app-router`. The adapter returns app-local bound +components and handoff helpers; your app still owns Contentful fetching, consent policy, cache keys, +and where personalized output is cached. If you use the Pages Router, use the [Next.js Pages Router guide](./integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md) instead. ## Quick start -Most App Router + Contentful sites share one shape: you fetch a **page** entry and render its -content through your own components. This quick start assumes that shape. In the snippets that -change an existing file, lines prefixed with `+` are what you add and the rest is a typical app for -context — match the additions to your own file rather than pasting the whole block. If your app is -shaped differently, the change is the same wherever an entry becomes a component; see -[Personalizing first paint on the server](#personalizing-first-paint-on-the-server). - -It proves one result: **one section renders its personalized variant in the server HTML.** It -assumes your app may personalize on startup. If personalization must wait for consent, keep this -structure and add the [Consent, identity, profile, and reset](#consent-identity-profile-and-reset) -step before you ship. +This quick start assumes an App Router route already fetches a Contentful entry and renders it with +your own component. The proof is one entry whose variant appears in View Source and stays stable +after hydration. Consent is granted on the server and browser only to prove the wiring; replace it +in [Consent, identity, profile, and reset](#consent-identity-profile-and-reset). -1. Install the adapter package. +1. Install the package and keep `contentful` app-owned. **Copy this:** ```sh - pnpm add @contentful/optimization-nextjs + pnpm add @contentful/optimization-nextjs contentful ``` -2. Add the browser-visible Optimization values to `.env.local`. Copy the client id and environment - from your Contentful Optimization setup; `main` is the usual environment unless your setup uses - another one. These values are not delivery tokens or secrets. +2. Create one bound Optimization module. `NEXT_PUBLIC_OPTIMIZATION_CLIENT_ID` is reader-owned + browser-visible config. The consent values below are a quick-start policy shortcut. **Adapt this to your use case:** - ```dotenv - NEXT_PUBLIC_OPTIMIZATION_CLIENT_ID=replace-with-your-client-id - NEXT_PUBLIC_OPTIMIZATION_ENVIRONMENT=main - ``` - -3. Create one module that binds the SDK to your config. You do this once and import from it - everywhere — the resulting components are _bound_ because they carry your config. Use the same - environment-variable convention your app already uses for Contentful. The snippets import it as - `@/src/lib/optimization`, which assumes the file is at `src/lib/optimization.ts` and your - `tsconfig` maps `@/*` to the project root — adjust the specifier to match your own `paths` (for - example `@/lib/optimization` if your alias points at a top-level `lib/`). - - **Adapt this to your use case:** replace the placeholder values and the import path; the config - keys are explained in [How the SDK fits your app](#how-the-sdk-fits-your-app). - - ```ts - // src/lib/optimization.ts + ```tsx + // lib/optimization.ts import { createNextjsAppRouterOptimization } from '@contentful/optimization-nextjs/app-router' - - export const APP_LOCALE = 'en-US' - - export const { proxy, NextAppAutoPageTracker, OptimizationRoot, OptimizedEntry } = - createNextjsAppRouterOptimization({ - clientId: process.env.NEXT_PUBLIC_OPTIMIZATION_CLIENT_ID ?? '', - environment: process.env.NEXT_PUBLIC_OPTIMIZATION_ENVIRONMENT ?? 'main', - locale: APP_LOCALE, - // consent: allowed to personalize and send events for this visitor. - // persistenceConsent: allowed to store a profile-id cookie so results stay consistent. - defaults: { consent: true, persistenceConsent: true }, - server: { - enabled: true, // resolve variants on the server, for first paint - consent: { events: true, persistence: true }, - }, - app: { name: 'my-next-app', version: '1.0.0' }, - // contentful: { client }, // opt-in: enables the entryId managed-fetch path - }) + import { contentfulClient } from './contentful' + + export const { + NextAppAutoPageTracker, + OptimizationRoot, + OptimizedEntry, + createRequestHandoff, + createHandoffFromSelections, + getServerTrackingAttributes, + resolveEntriesForSelections, + } = createNextjsAppRouterOptimization({ + clientId: process.env.NEXT_PUBLIC_OPTIMIZATION_CLIENT_ID!, + environment: process.env.CONTENTFUL_ENVIRONMENT ?? 'main', + locale: 'en-US', + contentful: { client: contentfulClient }, + consent: { + server: { events: true, persistence: true }, + clientDefaults: { consent: true, persistenceConsent: true }, + }, + }) ``` -4. Add the request handler so the SDK runs before your pages render. Next.js executes this file on - every matching request; the SDK's `proxy` reads the visitor's cookies, asks the Experience API - who they are, and stores that identity (an anonymous **profile** id) in the `ctfl-opt-aid` cookie - so the same visitor gets consistent variants next time. You are only mounting it — not writing - that logic. +3. Forward the original request URL so the root layout can build a stable route key. `proxy.ts` is a + Next.js file name; the `x-ctfl-opt-request-url` header is reader-owned. - Next.js is version-specific about both the filename and the export name: Next.js 16 loads a - `proxy` export from **`proxy.ts`**; Next.js 15 loads a `middleware` export from - **`middleware.ts`**. Get either wrong and the handler silently never runs — and because - `server.enabled` is `true`, `OptimizationRoot` then throws instead of falling back to baseline. - See [Request context and the profile cookie](#request-context-and-the-profile-cookie). - - **Copy this:** Next.js 16. + **Copy this:** ```ts - // proxy.ts — at your Next.js app root - export { proxy } from './src/lib/optimization' + // proxy.ts + import { NextResponse, type NextRequest } from 'next/server' - export const config = { - matcher: ['/((?!_next/static|_next/image|favicon.ico|api).*)'], - } - ``` - - **Copy this:** Next.js 15 uses the same handler, aliased to the `middleware` export that Next.js - 15 looks for. + export function proxy(request: NextRequest) { + const requestHeaders = new Headers(request.headers) + requestHeaders.set('x-ctfl-opt-request-url', request.url) - ```ts - // middleware.ts — at your Next.js app root - export { proxy as middleware } from './src/lib/optimization' + return NextResponse.next({ + request: { headers: requestHeaders }, + }) + } export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico|api).*)'], } ``` -5. Wrap your layout in `OptimizationRoot`, and put the page tracker inside it. **Keep everything - your layout already renders** — header, footer, providers, fonts, styles. You are adding a - wrapper, not replacing the file. Put the root around everything inside `` (chrome included) - so header, footer, or announcement content can be personalized later too. +4. Wrap the app in the bound root and pass the request handoff. `ctfl-opt-aid` is the SDK-owned + anonymous profile cookie; your code reads it only through the helper inputs. - **Adapt this to your use case:** the `+` lines are the additions; the rest is a typical layout - for context. + **Adapt this to your use case:** - ```tsx + ```diff // app/layout.tsx - +import { NextAppAutoPageTracker, OptimizationRoot } from '@/src/lib/optimization' + +import { cookies, headers } from 'next/headers' +import { Suspense } from 'react' + +import { NextAppAutoPageTracker, OptimizationRoot, createRequestHandoff } from '@/lib/optimization' export default async function RootLayout({ children }: { children: React.ReactNode }) { - const settings = await getSiteSettings() // your existing code stays + + const requestHeaders = await headers() + + const requestUrl = requestHeaders.get('x-ctfl-opt-request-url') ?? 'https://example.com/' + + const routeKey = new URL(requestUrl).pathname + + const handoff = await createRequestHandoff({ + + cache: { scope: 'private-request' }, + + hydration: 'preserve-server', + + pagePayload: { properties: { path: routeKey } }, + + request: { + + cookies: await cookies(), + + headers: requestHeaders, + + url: requestUrl, + + }, + + }) + return ( - + - + {/* Suspense is required: the tracker reads useSearchParams(), which Next.js - + only allows inside a Suspense boundary. */} + - {children} + + ({ properties: { path: routeKey } })} + + handoff={handoff} + + routeKey={routeKey} + + > + - + {/* "skip": the server already reported this page view — don't report it twice. */} + + -
- {children} -