diff --git a/AGENTS.md b/AGENTS.md index 8f7e1adb9..be4d4be2d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -190,6 +190,11 @@ Native and E2E examples; narrow with test-file, suite, scheme, or flow arguments remove behavior, narrow public APIs, obscure readable code, add one-off shims, or change source structure for bundle size unless the requester or maintainer explicitly approves that exact proposal. +- When reviewing an existing changeset, assume included bundle-size budget changes already have the + required requester or maintainer approval unless the user explicitly asks to audit approval. Do not + flag budget bumps as findings, risks, or noteworthy by themselves; only report concrete validation + problems such as a failing size check, a budget applied to the wrong bundle, or inconsistent + package policy. - Do not infer bundle-size tradeoff approval from general requests such as "fix CI", "make checks pass", "get under budget", or an implementation request. - Stop and surface the remaining overage when the remaining options would reduce maintainability, diff --git a/documentation/authoring/blueprints/android-compose.md b/documentation/authoring/blueprints/android-compose.md index 9def363ca..45d6e8137 100644 --- a/documentation/authoring/blueprints/android-compose.md +++ b/documentation/authoring/blueprints/android-compose.md @@ -19,7 +19,7 @@ guide: ../../guides/integrating-the-optimization-android-sdk-in-a-compose-app.md - **Deliberate simplifications:** Consent starts granted; entry rendering is deferred to Core because the app must supply the Contentful fetch. - **Explainer switches:** profile point = include; managed-fetch clause = omit. -- **Fact sources:** [setup](../../internal/sdk-knowledge/native/android.md#setup--factory), +- **Fact sources:** [setup](../../internal/sdk-knowledge/native/android.md#setup--initialization-and-binding), [events](../../internal/sdk-knowledge/native/android.md#events--tracking), [package](../../internal/sdk-knowledge/native/android.md#package--entry-points). @@ -37,22 +37,22 @@ guide: ../../guides/integrating-the-optimization-android-sdk-in-a-compose-app.md ## Section map -| Section | Category | Purpose | Must teach or show | Fact sources | -| ------------------------------------------------ | ------------------------------ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Install and initialize `OptimizationRoot` | Required for first integration | Establish the SDK-owned client and the Compose provider tree. | Maven Central dependency; `OptimizationRoot` diff; `LocalOptimizationClient` access; loading gate; suspend init and Flow surfaces. | [setup](../../internal/sdk-knowledge/native/android.md#setup--factory), [package](../../internal/sdk-knowledge/native/android.md#package--entry-points) | -| Consent and privacy-policy handoff | Common but policy-dependent | Replace the quick-start default-on shortcut and explain the two axes. | Two-axis example; boolean versus split consent; pre-consent allow-list; app-owned policy. | [consent](../../internal/sdk-knowledge/native/android.md#consent--persistence) | -| Contentful entry fetching and locale shape | Required for first integration | Establish the app-owned single-locale fetch that feeds resolution. | App owns the CDA fetch (no managed path); single-locale and include depth; SDK locale versus CDA locale. | [render](../../internal/sdk-knowledge/native/android.md#render--entry-resolution), [identifiers](../../internal/sdk-knowledge/native/android.md#identifier-ownership) | -| Entry resolution and fallback rendering | Required for first integration | Complete the entry half of Milestone 1 through `OptimizedEntry`. | Render-lambda example; direct suspend `resolveOptimizedEntry` alternative; baseline fallback; loading treatment. | [render](../../internal/sdk-knowledge/native/android.md#render--entry-resolution), [fallback](../../internal/sdk-knowledge/native/android.md#failure--fallback-behavior) | -| Screen and navigation tracking | Required for first integration | Deepen the quick-start screen proof across Compose navigation. | `ScreenTrackingEffect` placement; `trackCurrentScreen` for dynamic names and route keys; dedupe; one-path warning. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | -| Entry interaction tracking | Common but policy-dependent | Explain viewport-based views and taps and their opt-outs. | `OptimizationLazyColumn`; thresholds; tap versus app click; `trackViews`/`trackTaps` opt-outs; `onTap` baseline note. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | -| Identity, profile continuity, and reset | Common but policy-dependent | Integrate login/logout and profile continuity. | Identify/reset example; `SharedPreferences` continuity; reset preserves consent. | [consent](../../internal/sdk-knowledge/native/android.md#consent--persistence), [identifiers](../../internal/sdk-knowledge/native/android.md#identifier-ownership) | -| Custom events and analytics diagnostics | Optional | Cover business events and the debug/forwarding seam. | `track` example; `SharedFlow` `eventStream` replay-buffer semantics; `blockedEventStream`; forwarding link. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | -| Custom Flags and MergeTag rendering | Optional | Cover content-model-dependent reads. | `getFlag` versus `observeFlag` `StateFlow`; `getMergeTagValue`; flag-view exposure governance. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking), [render](../../internal/sdk-knowledge/native/android.md#render--entry-resolution) | -| Live updates | Optional | Allow mounted entries to re-resolve. | Root and per-entry scopes; locked-by-default explanation; preview forces live; panel-close snapshot. | [render](../../internal/sdk-knowledge/native/android.md#render--entry-resolution), [runtime](../../internal/sdk-knowledge/native/android.md#version--runtime-quirks) | -| Preview panel | Optional | Add the debug/authoring surface. | Debug gating; `PreviewPanelConfig`; `PreviewContentfulClient` for names; overlay in the Compose tree. | [runtime](../../internal/sdk-knowledge/native/android.md#version--runtime-quirks) | -| Strict event policy and queue controls | Advanced or production-only | Expose production privacy and delivery posture. | `allowedEventTypes = emptyList()`; `onEventBlocked`/`blockedEventStream` proof; queue policy; blocked not replayed. | [consent](../../internal/sdk-knowledge/native/android.md#consent--persistence), [setup](../../internal/sdk-knowledge/native/android.md#setup--factory) | -| Offline delivery and lifecycle flushing | Advanced or production-only | Explain constrained-network delivery. | Reachability and background flush; in-memory queue plus 100-event cap; no durable outbox; deterministic controls. | [runtime](../../internal/sdk-knowledge/native/android.md#version--runtime-quirks), [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | -| Content caching and hybrid continuity boundaries | Advanced or production-only | Bound app-owned caching against SDK persistence. | App owns CDA caching and locale-aware keys; native has no server/browser continuity handoff. | [identifiers](../../internal/sdk-knowledge/native/android.md#identifier-ownership), [runtime](../../internal/sdk-knowledge/native/android.md#version--runtime-quirks) | +| Section | Category | Purpose | Must teach or show | Fact sources | +| ------------------------------------------------ | ------------------------------ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Install and initialize `OptimizationRoot` | Required for first integration | Establish the SDK-owned client and the Compose provider tree. | Maven Central dependency; `OptimizationRoot` diff; `LocalOptimizationClient` access; loading gate; suspend init and Flow surfaces. | [setup](../../internal/sdk-knowledge/native/android.md#setup--initialization-and-binding), [package](../../internal/sdk-knowledge/native/android.md#package--entry-points) | +| Consent and privacy-policy handoff | Common but policy-dependent | Replace the quick-start default-on shortcut and explain the two axes. | Two-axis example; boolean versus split consent; pre-consent allow-list; app-owned policy. | [consent](../../internal/sdk-knowledge/native/android.md#consent--persistence) | +| Contentful entry fetching and locale shape | Required for first integration | Establish the app-owned single-locale fetch that feeds resolution. | App owns the CDA fetch (no managed path); single-locale and include depth; SDK locale versus CDA locale. | [render](../../internal/sdk-knowledge/native/android.md#render--entry-resolution), [identifiers](../../internal/sdk-knowledge/native/android.md#identifier-ownership) | +| Entry resolution and fallback rendering | Required for first integration | Complete the entry half of Milestone 1 through `OptimizedEntry`. | Render-lambda example; direct suspend `resolveOptimizedEntry` alternative; baseline fallback; loading treatment. | [render](../../internal/sdk-knowledge/native/android.md#render--entry-resolution), [fallback](../../internal/sdk-knowledge/native/android.md#failure--fallback-behavior) | +| Screen and navigation tracking | Required for first integration | Deepen the quick-start screen proof across Compose navigation. | `ScreenTrackingEffect` placement; `trackCurrentScreen` for dynamic names and route keys; dedupe; one-path warning. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | +| Entry interaction tracking | Common but policy-dependent | Explain viewport-based views and taps and their opt-outs. | `OptimizationLazyColumn`; thresholds; tap versus app click; `trackViews`/`trackTaps` opt-outs; `onTap` baseline note. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | +| Identity, profile continuity, and reset | Common but policy-dependent | Integrate login/logout and profile continuity. | Identify/reset example; `SharedPreferences` continuity; reset preserves consent. | [consent](../../internal/sdk-knowledge/native/android.md#consent--persistence), [identifiers](../../internal/sdk-knowledge/native/android.md#identifier-ownership) | +| Custom events and analytics diagnostics | Optional | Cover business events and the debug/forwarding seam. | `track` example; `SharedFlow` `eventStream` replay-buffer semantics; `blockedEventStream`; forwarding link. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | +| Custom Flags and MergeTag rendering | Optional | Cover content-model-dependent reads. | `getFlag` versus `observeFlag` `StateFlow`; `getMergeTagValue`; flag-view exposure governance. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking), [render](../../internal/sdk-knowledge/native/android.md#render--entry-resolution) | +| Live updates | Optional | Allow mounted entries to re-resolve. | Root and per-entry scopes; locked-by-default explanation; preview forces live; panel-close snapshot. | [render](../../internal/sdk-knowledge/native/android.md#render--entry-resolution), [runtime](../../internal/sdk-knowledge/native/android.md#version--runtime-quirks) | +| Preview panel | Optional | Add the debug/authoring surface. | Debug gating; `PreviewPanelConfig`; `PreviewContentfulClient` for names; overlay in the Compose tree. | [runtime](../../internal/sdk-knowledge/native/android.md#version--runtime-quirks) | +| Strict event policy and queue controls | Advanced or production-only | Expose production privacy and delivery posture. | `allowedEventTypes = emptyList()`; `onEventBlocked`/`blockedEventStream` proof; queue policy; blocked not replayed. | [consent](../../internal/sdk-knowledge/native/android.md#consent--persistence), [setup](../../internal/sdk-knowledge/native/android.md#setup--initialization-and-binding) | +| Offline delivery and lifecycle flushing | Advanced or production-only | Explain constrained-network delivery. | Reachability and background flush; in-memory queue plus 100-event cap; no durable outbox; deterministic controls. | [runtime](../../internal/sdk-knowledge/native/android.md#version--runtime-quirks), [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | +| Content caching and hybrid continuity boundaries | Advanced or production-only | Bound app-owned caching against SDK persistence. | App owns CDA caching and locale-aware keys; native has no server/browser continuity handoff. | [identifiers](../../internal/sdk-knowledge/native/android.md#identifier-ownership), [runtime](../../internal/sdk-knowledge/native/android.md#version--runtime-quirks) | ## SDK-specific authoring overrides @@ -62,7 +62,7 @@ guide: ../../guides/integrating-the-optimization-android-sdk-in-a-compose-app.md - Prove "init plus one accepted screen event" rather than an entry render in the quick start: Android has no managed fetch, so an entry-render proof would force an app-specific Kotlin CDA fetch into the quick start. Entry rendering moves to Core. Facts: - [setup](../../internal/sdk-knowledge/native/android.md#setup--factory), + [setup](../../internal/sdk-knowledge/native/android.md#setup--initialization-and-binding), [events](../../internal/sdk-knowledge/native/android.md#events--tracking). - Keep the dependency/build step inline in the quick start: add the Maven Central dependency and run a Gradle build; there is no separate native prebuild. Facts: @@ -78,7 +78,7 @@ guide: ../../guides/integrating-the-optimization-android-sdk-in-a-compose-app.md | Reader symptom | Why it belongs | Fact sources | | ------------------------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `No OptimizationClient provided` | Calling SDK helpers outside `OptimizationRoot`. | [setup](../../internal/sdk-knowledge/native/android.md#setup--factory) | +| `No OptimizationClient provided` | Calling SDK helpers outside `OptimizationRoot`. | [setup](../../internal/sdk-knowledge/native/android.md#setup--initialization-and-binding) | | Entries always render baseline | Common payload, consent, and locale symptoms for entry rendering. | [render](../../internal/sdk-knowledge/native/android.md#render--entry-resolution), [consent](../../internal/sdk-knowledge/native/android.md#consent--persistence) | | Entry view or tap events are missing | Makes viewport thresholds, list tracking, and consent actionable. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | | Screen events duplicate or go missing | Effect placement and route-key dedupe. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | diff --git a/documentation/authoring/blueprints/android-views.md b/documentation/authoring/blueprints/android-views.md index 0807028e0..f87621994 100644 --- a/documentation/authoring/blueprints/android-views.md +++ b/documentation/authoring/blueprints/android-views.md @@ -20,7 +20,7 @@ guide: ../../guides/integrating-the-optimization-android-sdk-in-a-views-app.md - **Deliberate simplifications:** Consent starts granted; entry rendering is deferred to Core because the app must supply the Contentful fetch. - **Explainer switches:** profile point = include; managed-fetch clause = omit. -- **Fact sources:** [setup](../../internal/sdk-knowledge/native/android.md#setup--factory), +- **Fact sources:** [setup](../../internal/sdk-knowledge/native/android.md#setup--initialization-and-binding), [events](../../internal/sdk-knowledge/native/android.md#events--tracking). ## Milestone contract @@ -32,24 +32,24 @@ guide: ../../guides/integrating-the-optimization-android-sdk-in-a-views-app.md identity, Custom Flags, live updates, preview, and offline delivery. - **Boundary:** Mandatory application/activity wiring plus the first accepted event and entry render versus opt-in application policy and capabilities. -- **Fact sources:** [setup](../../internal/sdk-knowledge/native/android.md#setup--factory), +- **Fact sources:** [setup](../../internal/sdk-knowledge/native/android.md#setup--initialization-and-binding), [runtime](../../internal/sdk-knowledge/native/android.md#version--runtime-quirks). ## Section map -| Section | Category | Purpose | Must teach or show | Fact sources | -| ------------------------------------------------------------ | ------------------------------ | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| SDK installation and process-wide client | Required for first integration | Establish the package and the app-scoped `OptimizationManager`. | Maven Central dependency; `Application` init diff plus manifest registration; readiness await; required versus defaulted config keys. | [setup](../../internal/sdk-knowledge/native/android.md#setup--factory), [package](../../internal/sdk-knowledge/native/android.md#package--entry-points) | -| Consent and privacy-policy handoff | Common but policy-dependent | Replace the quick-start default-on shortcut and explain the two axes. | Two-axis example; boolean versus split consent; pre-consent allow-list; app-owned policy. | [consent](../../internal/sdk-knowledge/native/android.md#consent--persistence) | -| Contentful fetching and entry resolution | Required for first integration | Establish the app-owned fetch and the view-based resolver. | App owns the CDA fetch (no managed path); single-locale and include depth; `OptimizedEntryView` renderer; baseline fallback. | [render](../../internal/sdk-knowledge/native/android.md#render--entry-resolution), [fallback](../../internal/sdk-knowledge/native/android.md#failure--fallback-behavior) | -| Screen and navigation tracking | Required for first integration | Deepen the quick-start screen proof across activity navigation. | `ScreenTracker.trackScreen` from `onResume`; route-key dedupe; `screen` versus `trackCurrentScreen` choice. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | -| Entry interaction tracking | Common but policy-dependent | Explain view- and tap-tracking and list integration. | `OptimizedEntryView` view/tap tracking; `TrackingRecyclerView` for lists; opt-outs; duplicate prevention. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | -| Identity, profile continuity, and reset | Common but policy-dependent | Integrate login/logout and profile continuity. | Identify/reset example; `SharedPreferences` continuity; reset preserves consent. | [consent](../../internal/sdk-knowledge/native/android.md#consent--persistence), [identifiers](../../internal/sdk-knowledge/native/android.md#identifier-ownership) | -| Custom events and analytics diagnostics | Optional | Cover business events and the debug/forwarding seam. | `track` example; `SharedFlow` `eventStream` replay-buffer semantics; `blockedEventStream`; forwarding link. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | -| Custom Flags and MergeTag rendering | Optional | Cover content-model-dependent reads. | `getFlag` versus `observeFlag` `StateFlow`; `getMergeTagValue`; flag-view exposure governance. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking), [render](../../internal/sdk-knowledge/native/android.md#render--entry-resolution) | -| Live updates and locked variants | Optional | Let Views apps choose live or locked content. | Locked snapshot versus live subscription; `null` versus explicit selections; preview forces redraw. | [render](../../internal/sdk-knowledge/native/android.md#render--entry-resolution), [runtime](../../internal/sdk-knowledge/native/android.md#version--runtime-quirks) | -| Preview panel | Optional | Add the Views debug/authoring surface. | Debug gating; `OptimizationManager.attachPreviewPanel`; `PreviewContentfulClient` for names; same client. | [runtime](../../internal/sdk-knowledge/native/android.md#version--runtime-quirks) | -| Offline delivery, queue observability, and app-owned caching | Advanced or production-only | Expose production delivery posture and the app cache boundary. | Default offline path; queue policy and callbacks; in-memory 100-event cap; app-owned content caching. | [runtime](../../internal/sdk-knowledge/native/android.md#version--runtime-quirks), [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | +| Section | Category | Purpose | Must teach or show | Fact sources | +| ------------------------------------------------------------ | ------------------------------ | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| SDK installation and process-wide client | Required for first integration | Establish the package and the app-scoped `OptimizationManager`. | Maven Central dependency; `Application` init diff plus manifest registration; readiness await; required versus defaulted config keys. | [setup](../../internal/sdk-knowledge/native/android.md#setup--initialization-and-binding), [package](../../internal/sdk-knowledge/native/android.md#package--entry-points) | +| Consent and privacy-policy handoff | Common but policy-dependent | Replace the quick-start default-on shortcut and explain the two axes. | Two-axis example; boolean versus split consent; pre-consent allow-list; app-owned policy. | [consent](../../internal/sdk-knowledge/native/android.md#consent--persistence) | +| Contentful fetching and entry resolution | Required for first integration | Establish the app-owned fetch and the view-based resolver. | App owns the CDA fetch (no managed path); single-locale and include depth; `OptimizedEntryView` renderer; baseline fallback. | [render](../../internal/sdk-knowledge/native/android.md#render--entry-resolution), [fallback](../../internal/sdk-knowledge/native/android.md#failure--fallback-behavior) | +| Screen and navigation tracking | Required for first integration | Deepen the quick-start screen proof across activity navigation. | `ScreenTracker.trackScreen` from `onResume`; route-key dedupe; `screen` versus `trackCurrentScreen` choice. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | +| Entry interaction tracking | Common but policy-dependent | Explain view- and tap-tracking and list integration. | `OptimizedEntryView` view/tap tracking; `TrackingRecyclerView` for lists; opt-outs; duplicate prevention. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | +| Identity, profile continuity, and reset | Common but policy-dependent | Integrate login/logout and profile continuity. | Identify/reset example; `SharedPreferences` continuity; reset preserves consent. | [consent](../../internal/sdk-knowledge/native/android.md#consent--persistence), [identifiers](../../internal/sdk-knowledge/native/android.md#identifier-ownership) | +| Custom events and analytics diagnostics | Optional | Cover business events and the debug/forwarding seam. | `track` example; `SharedFlow` `eventStream` replay-buffer semantics; `blockedEventStream`; forwarding link. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | +| Custom Flags and MergeTag rendering | Optional | Cover content-model-dependent reads. | `getFlag` versus `observeFlag` `StateFlow`; `getMergeTagValue`; flag-view exposure governance. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking), [render](../../internal/sdk-knowledge/native/android.md#render--entry-resolution) | +| Live updates and locked variants | Optional | Let Views apps choose live or locked content. | Locked snapshot versus live subscription; `null` versus explicit selections; preview forces redraw. | [render](../../internal/sdk-knowledge/native/android.md#render--entry-resolution), [runtime](../../internal/sdk-knowledge/native/android.md#version--runtime-quirks) | +| Preview panel | Optional | Add the Views debug/authoring surface. | Debug gating; `OptimizationManager.attachPreviewPanel`; `PreviewContentfulClient` for names; same client. | [runtime](../../internal/sdk-knowledge/native/android.md#version--runtime-quirks) | +| Offline delivery, queue observability, and app-owned caching | Advanced or production-only | Expose production delivery posture and the app cache boundary. | Default offline path; queue policy and callbacks; in-memory 100-event cap; app-owned content caching. | [runtime](../../internal/sdk-knowledge/native/android.md#version--runtime-quirks), [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | ## SDK-specific authoring overrides @@ -59,11 +59,11 @@ guide: ../../guides/integrating-the-optimization-android-sdk-in-a-views-app.md - Prove "init plus one accepted screen event" in the quick start, with a distinguishable success signal and a failure diagnostic — do not use an unfalsifiable "renders baseline or variant" check. Entry rendering moves to Core because Android has no managed fetch. Facts: - [setup](../../internal/sdk-knowledge/native/android.md#setup--factory), + [setup](../../internal/sdk-knowledge/native/android.md#setup--initialization-and-binding), [events](../../internal/sdk-knowledge/native/android.md#events--tracking). - Show the `Application` runtime root as a `+`/`-` diff and include the `AndroidManifest.xml` `android:name` registration inline: without it `onCreate` never runs and the SDK never initializes. - Facts: [setup](../../internal/sdk-knowledge/native/android.md#setup--factory). + Facts: [setup](../../internal/sdk-knowledge/native/android.md#setup--initialization-and-binding). - Split screen tracking from custom events into distinct sections with distinct categories (screen is Required/Core; custom events are Optional); do not bundle them under one section. Facts: [events](../../internal/sdk-knowledge/native/android.md#events--tracking). @@ -78,7 +78,7 @@ guide: ../../guides/integrating-the-optimization-android-sdk-in-a-views-app.md | Reader symptom | Why it belongs | Fact sources | | ----------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| SDK never initializes / events never emit | Missing manifest `android:name` registration or readiness await. | [setup](../../internal/sdk-knowledge/native/android.md#setup--factory) | +| SDK never initializes / events never emit | Missing manifest `android:name` registration or readiness await. | [setup](../../internal/sdk-knowledge/native/android.md#setup--initialization-and-binding) | | Entries always render the baseline | Common payload and resolver symptoms. | [render](../../internal/sdk-knowledge/native/android.md#render--entry-resolution), [identifiers](../../internal/sdk-knowledge/native/android.md#identifier-ownership) | | Tap or view events do not appear | Makes consent, list wiring, and visibility actionable. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | | Screen events appear more than once | Activity lifecycle repeats and route-key dedupe. | [events](../../internal/sdk-knowledge/native/android.md#events--tracking) | diff --git a/documentation/authoring/blueprints/ios-swiftui.md b/documentation/authoring/blueprints/ios-swiftui.md index f74a412f6..1e81ebcdf 100644 --- a/documentation/authoring/blueprints/ios-swiftui.md +++ b/documentation/authoring/blueprints/ios-swiftui.md @@ -19,7 +19,7 @@ guide: ../../guides/integrating-the-optimization-ios-sdk-in-a-swiftui-app.md - **Deliberate simplifications:** Consent starts granted; entry rendering is deferred to Core because the app must supply the Contentful fetch. - **Explainer switches:** profile point = include; managed-fetch clause = omit. -- **Fact sources:** [setup](../../internal/sdk-knowledge/native/ios.md#setup--factory), +- **Fact sources:** [setup](../../internal/sdk-knowledge/native/ios.md#setup--initialization-and-binding), [events](../../internal/sdk-knowledge/native/ios.md#events--tracking), [package](../../internal/sdk-knowledge/native/ios.md#package--entry-points). @@ -37,21 +37,21 @@ guide: ../../guides/integrating-the-optimization-ios-sdk-in-a-swiftui-app.md ## Section map -| Section | Category | Purpose | Must teach or show | Fact sources | -| ------------------------------------------ | ------------------------------ | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Install and initialize the SwiftUI root | Required for first integration | Establish the SDK-owned client and the SwiftUI provider tree. | SPM add and Xcode build/run; `OptimizationRoot` diff; `@EnvironmentObject` access; readiness gate; synchronous throwing init. | [setup](../../internal/sdk-knowledge/native/ios.md#setup--factory), [package](../../internal/sdk-knowledge/native/ios.md#package--entry-points) | -| Consent and privacy-policy handoff | Common but policy-dependent | Replace the quick-start default-on shortcut and explain the two axes. | Two-axis example; boolean versus split consent; pre-consent allow-list; app-owned policy. | [consent](../../internal/sdk-knowledge/native/ios.md#consent--persistence) | -| Contentful entry fetching and locale shape | Required for first integration | Establish the app-owned single-locale fetch that feeds resolution. | App owns the CDA fetch (no managed path); single-locale and include depth; SDK locale versus CDA locale. | [render](../../internal/sdk-knowledge/native/ios.md#render--entry-resolution), [identifiers](../../internal/sdk-knowledge/native/ios.md#identifier-ownership) | -| Entry resolution and fallback rendering | Required for first integration | Complete the entry half of Milestone 1 through `OptimizedEntry`. | Render-closure example and cast; direct `resolveOptimizedEntry` alternative; baseline fallback; loading treatment. | [render](../../internal/sdk-knowledge/native/ios.md#render--entry-resolution), [fallback](../../internal/sdk-knowledge/native/ios.md#failure--fallback-behavior) | -| Screen events and SwiftUI navigation | Required for first integration | Deepen the quick-start screen proof across SwiftUI navigation. | `.trackScreen` placement; `trackCurrentScreen` for dynamic names and route keys; dedupe; choose-one-path warning. | [events](../../internal/sdk-knowledge/native/ios.md#events--tracking) | -| Entry interaction tracking | Common but policy-dependent | Explain viewport-based views and taps and their opt-outs. | Scroll provider; thresholds; tap versus SwiftUI `Button`; `trackViews`/`trackTaps` opt-outs; `onTap` gating. | [events](../../internal/sdk-knowledge/native/ios.md#events--tracking) | -| Identity, profile state, and reset | Common but policy-dependent | Integrate login/logout and profile continuity. | Identify/reset example; `UserDefaults` continuity; reset preserves consent. | [consent](../../internal/sdk-knowledge/native/ios.md#consent--persistence), [identifiers](../../internal/sdk-knowledge/native/ios.md#identifier-ownership) | -| Custom events and analytics diagnostics | Optional | Cover business events and the debug/forwarding seam. | `track` example; passthrough `eventStream` with subscribe-before-fire; `blockedEventStream`; forwarding link. | [events](../../internal/sdk-knowledge/native/ios.md#events--tracking) | -| Custom Flags and MergeTag rendering | Optional | Cover content-model-dependent reads. | `getFlag` versus `flagPublisher`; `getMergeTagValue`; flag-view exposure governance. | [events](../../internal/sdk-knowledge/native/ios.md#events--tracking), [render](../../internal/sdk-knowledge/native/ios.md#render--entry-resolution) | -| Live updates | Optional | Allow mounted entries to re-resolve. | Root and per-entry scopes; locked-by-default explanation; preview forces live; panel-close snapshot. | [render](../../internal/sdk-knowledge/native/ios.md#render--entry-resolution), [runtime](../../internal/sdk-knowledge/native/ios.md#version--runtime-quirks) | -| Preview panel | Optional | Add the debug/authoring surface. | Debug gating; `PreviewPanelConfig`; `PreviewContentfulClient` for names; overlay placement. | [runtime](../../internal/sdk-knowledge/native/ios.md#version--runtime-quirks) | -| Strict event policy and endpoint controls | Advanced or production-only | Expose production privacy and endpoint posture. | `allowedEventTypes: []`; `onEventBlocked`/`blockedEventStream` proof; API endpoint overrides; queue policy. | [consent](../../internal/sdk-knowledge/native/ios.md#consent--persistence), [setup](../../internal/sdk-knowledge/native/ios.md#setup--factory) | -| Offline delivery and lifecycle flushing | Advanced or production-only | Explain constrained-network delivery. | Reachability and background flush; in-memory queue plus 100-event cap; no durable outbox; `flush()` checkpoints. | [runtime](../../internal/sdk-knowledge/native/ios.md#version--runtime-quirks), [events](../../internal/sdk-knowledge/native/ios.md#events--tracking) | +| Section | Category | Purpose | Must teach or show | Fact sources | +| ------------------------------------------ | ------------------------------ | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Install and initialize the SwiftUI root | Required for first integration | Establish the SDK-owned client and the SwiftUI provider tree. | SPM add and Xcode build/run; `OptimizationRoot` diff; `@EnvironmentObject` access; readiness gate; synchronous throwing init. | [setup](../../internal/sdk-knowledge/native/ios.md#setup--initialization-and-binding), [package](../../internal/sdk-knowledge/native/ios.md#package--entry-points) | +| Consent and privacy-policy handoff | Common but policy-dependent | Replace the quick-start default-on shortcut and explain the two axes. | Two-axis example; boolean versus split consent; pre-consent allow-list; app-owned policy. | [consent](../../internal/sdk-knowledge/native/ios.md#consent--persistence) | +| Contentful entry fetching and locale shape | Required for first integration | Establish the app-owned single-locale fetch that feeds resolution. | App owns the CDA fetch (no managed path); single-locale and include depth; SDK locale versus CDA locale. | [render](../../internal/sdk-knowledge/native/ios.md#render--entry-resolution), [identifiers](../../internal/sdk-knowledge/native/ios.md#identifier-ownership) | +| Entry resolution and fallback rendering | Required for first integration | Complete the entry half of Milestone 1 through `OptimizedEntry`. | Render-closure example and cast; direct `resolveOptimizedEntry` alternative; baseline fallback; loading treatment. | [render](../../internal/sdk-knowledge/native/ios.md#render--entry-resolution), [fallback](../../internal/sdk-knowledge/native/ios.md#failure--fallback-behavior) | +| Screen events and SwiftUI navigation | Required for first integration | Deepen the quick-start screen proof across SwiftUI navigation. | `.trackScreen` placement; `trackCurrentScreen` for dynamic names and route keys; dedupe; choose-one-path warning. | [events](../../internal/sdk-knowledge/native/ios.md#events--tracking) | +| Entry interaction tracking | Common but policy-dependent | Explain viewport-based views and taps and their opt-outs. | Scroll provider; thresholds; tap versus SwiftUI `Button`; `trackViews`/`trackTaps` opt-outs; `onTap` gating. | [events](../../internal/sdk-knowledge/native/ios.md#events--tracking) | +| Identity, profile state, and reset | Common but policy-dependent | Integrate login/logout and profile continuity. | Identify/reset example; `UserDefaults` continuity; reset preserves consent. | [consent](../../internal/sdk-knowledge/native/ios.md#consent--persistence), [identifiers](../../internal/sdk-knowledge/native/ios.md#identifier-ownership) | +| Custom events and analytics diagnostics | Optional | Cover business events and the debug/forwarding seam. | `track` example; passthrough `eventStream` with subscribe-before-fire; `blockedEventStream`; forwarding link. | [events](../../internal/sdk-knowledge/native/ios.md#events--tracking) | +| Custom Flags and MergeTag rendering | Optional | Cover content-model-dependent reads. | `getFlag` versus `flagPublisher`; `getMergeTagValue`; flag-view exposure governance. | [events](../../internal/sdk-knowledge/native/ios.md#events--tracking), [render](../../internal/sdk-knowledge/native/ios.md#render--entry-resolution) | +| Live updates | Optional | Allow mounted entries to re-resolve. | Root and per-entry scopes; locked-by-default explanation; preview forces live; panel-close snapshot. | [render](../../internal/sdk-knowledge/native/ios.md#render--entry-resolution), [runtime](../../internal/sdk-knowledge/native/ios.md#version--runtime-quirks) | +| Preview panel | Optional | Add the debug/authoring surface. | Debug gating; `PreviewPanelConfig`; `PreviewContentfulClient` for names; overlay placement. | [runtime](../../internal/sdk-knowledge/native/ios.md#version--runtime-quirks) | +| Strict event policy and endpoint controls | Advanced or production-only | Expose production privacy and endpoint posture. | `allowedEventTypes: []`; `onEventBlocked`/`blockedEventStream` proof; API endpoint overrides; queue policy. | [consent](../../internal/sdk-knowledge/native/ios.md#consent--persistence), [setup](../../internal/sdk-knowledge/native/ios.md#setup--initialization-and-binding) | +| Offline delivery and lifecycle flushing | Advanced or production-only | Explain constrained-network delivery. | Reachability and background flush; in-memory queue plus 100-event cap; no durable outbox; `flush()` checkpoints. | [runtime](../../internal/sdk-knowledge/native/ios.md#version--runtime-quirks), [events](../../internal/sdk-knowledge/native/ios.md#events--tracking) | ## SDK-specific authoring overrides @@ -61,7 +61,7 @@ guide: ../../guides/integrating-the-optimization-ios-sdk-in-a-swiftui-app.md - Prove "init plus one accepted screen event" rather than an entry render in the quick start: iOS has no managed fetch, so an entry-render proof would force an app-specific Swift CDA fetch into the quick start. Entry rendering moves to Core. Facts: - [setup](../../internal/sdk-knowledge/native/ios.md#setup--factory), + [setup](../../internal/sdk-knowledge/native/ios.md#setup--initialization-and-binding), [render](../../internal/sdk-knowledge/native/ios.md#render--entry-resolution). - Keep the native package/build step inline in the quick start: add the Swift Package in Xcode, then build and run on a simulator (there is no `pod install`). Facts: diff --git a/documentation/authoring/blueprints/ios-uikit.md b/documentation/authoring/blueprints/ios-uikit.md index 3fdc0d1df..2ac5a1225 100644 --- a/documentation/authoring/blueprints/ios-uikit.md +++ b/documentation/authoring/blueprints/ios-uikit.md @@ -20,7 +20,7 @@ guide: ../../guides/integrating-the-optimization-ios-sdk-in-a-uikit-app.md - **Deliberate simplifications:** Consent starts granted; entry rendering is deferred to Core because the app must supply the Contentful fetch. - **Explainer switches:** profile point = include; managed-fetch clause = omit. -- **Fact sources:** [setup](../../internal/sdk-knowledge/native/ios.md#setup--factory), +- **Fact sources:** [setup](../../internal/sdk-knowledge/native/ios.md#setup--initialization-and-binding), [events](../../internal/sdk-knowledge/native/ios.md#events--tracking). ## Milestone contract @@ -32,26 +32,26 @@ guide: ../../guides/integrating-the-optimization-ios-sdk-in-a-uikit-app.md identity, Custom Flags, live updates, preview, runtime locale changes, and offline delivery. - **Boundary:** Mandatory scene wiring plus the first accepted event and entry render versus opt-in application policy and capabilities. -- **Fact sources:** [setup](../../internal/sdk-knowledge/native/ios.md#setup--factory), +- **Fact sources:** [setup](../../internal/sdk-knowledge/native/ios.md#setup--initialization-and-binding), [runtime](../../internal/sdk-knowledge/native/ios.md#version--runtime-quirks). ## Section map -| Section | Category | Purpose | Must teach or show | Fact sources | -| ------------------------------------------------------------ | ------------------------------ | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Package installation and SDK configuration | Required for first integration | Establish the package and the one `OptimizationConfig`. | SPM add and Xcode build/run; required versus defaulted config keys; endpoint overrides only when non-default. | [setup](../../internal/sdk-knowledge/native/ios.md#setup--factory), [package](../../internal/sdk-knowledge/native/ios.md#package--entry-points) | -| Client lifetime and UIKit injection | Required for first integration | Own one client for the scene/app lifetime and inject it manually. | Create-and-initialize placement; manual injection through initializers; main-actor calling; readiness. | [setup](../../internal/sdk-knowledge/native/ios.md#setup--factory) | -| Consent and privacy-policy handoff | Common but policy-dependent | Replace the quick-start default-on shortcut and explain the two axes. | Two-axis example; boolean versus split consent; pre-consent allow-list; app-owned policy. | [consent](../../internal/sdk-knowledge/native/ios.md#consent--persistence) | -| Contentful fetching and entry resolution | Required for first integration | Establish the app-owned fetch and the imperative resolver. | App owns the CDA fetch (no managed path); single-locale and include depth; synchronous fail-soft resolve; baseline fallback. | [render](../../internal/sdk-knowledge/native/ios.md#render--entry-resolution), [fallback](../../internal/sdk-knowledge/native/ios.md#failure--fallback-behavior) | -| Screen and navigation tracking | Required for first integration | Deepen the quick-start screen proof across UIKit navigation. | `trackCurrentScreen` from `viewDidAppear`; route-key dedupe; `screen` versus `trackCurrentScreen` choice. | [events](../../internal/sdk-knowledge/native/ios.md#events--tracking) | -| Entry interaction tracking | Common but policy-dependent | Explain UIKit-owned taps and view timing. | Tap payload from stored resolution; `ViewTrackingController` timing model; app owns geometry; opt-outs and duplicate prevention. | [events](../../internal/sdk-knowledge/native/ios.md#events--tracking) | -| Identity, profile continuity, and reset | Common but policy-dependent | Integrate login/logout and profile continuity. | Identify/reset example; `UserDefaults` continuity; reset preserves consent. | [consent](../../internal/sdk-knowledge/native/ios.md#consent--persistence), [identifiers](../../internal/sdk-knowledge/native/ios.md#identifier-ownership) | -| Custom events and analytics diagnostics | Optional | Cover business events and the debug/forwarding seam. | `track` example; passthrough `eventStream` with subscribe-before-fire; `blockedEventStream`; forwarding link. | [events](../../internal/sdk-knowledge/native/ios.md#events--tracking) | -| Custom Flags and MergeTag rendering | Optional | Cover content-model-dependent reads. | `getFlag` versus `flagPublisher`; `getMergeTagValue`; flag-view exposure governance. | [events](../../internal/sdk-knowledge/native/ios.md#events--tracking), [render](../../internal/sdk-knowledge/native/ios.md#render--entry-resolution) | -| Live updates and locked variants | Optional | Let UIKit apps choose live or locked content. | Locked snapshot versus live subscription; `nil` versus explicit selections; preview forces redraw. | [render](../../internal/sdk-knowledge/native/ios.md#render--entry-resolution), [runtime](../../internal/sdk-knowledge/native/ios.md#version--runtime-quirks) | -| Preview panel | Optional | Add the UIKit debug/authoring surface. | Debug gating; `PreviewPanelViewController`; `PreviewContentfulClient` for names; same client instance. | [runtime](../../internal/sdk-knowledge/native/ios.md#version--runtime-quirks) | -| Runtime locale changes | Optional | Handle post-startup locale switches. | `setLocale` updates SDK locale only; refetch CDA and re-resolve; locale-aware cache keys. | [runtime](../../internal/sdk-knowledge/native/ios.md#version--runtime-quirks), [identifiers](../../internal/sdk-knowledge/native/ios.md#identifier-ownership) | -| Offline delivery, queue observability, and app-owned caching | Advanced or production-only | Expose production delivery posture and the app cache boundary. | Default offline path; queue policy and callbacks; in-memory 100-event cap; app-owned content caching. | [runtime](../../internal/sdk-knowledge/native/ios.md#version--runtime-quirks), [events](../../internal/sdk-knowledge/native/ios.md#events--tracking) | +| Section | Category | Purpose | Must teach or show | Fact sources | +| ------------------------------------------------------------ | ------------------------------ | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Package installation and SDK configuration | Required for first integration | Establish the package and the one `OptimizationConfig`. | SPM add and Xcode build/run; required versus defaulted config keys; endpoint overrides only when non-default. | [setup](../../internal/sdk-knowledge/native/ios.md#setup--initialization-and-binding), [package](../../internal/sdk-knowledge/native/ios.md#package--entry-points) | +| Client lifetime and UIKit injection | Required for first integration | Own one client for the scene/app lifetime and inject it manually. | Create-and-initialize placement; manual injection through initializers; main-actor calling; readiness. | [setup](../../internal/sdk-knowledge/native/ios.md#setup--initialization-and-binding) | +| Consent and privacy-policy handoff | Common but policy-dependent | Replace the quick-start default-on shortcut and explain the two axes. | Two-axis example; boolean versus split consent; pre-consent allow-list; app-owned policy. | [consent](../../internal/sdk-knowledge/native/ios.md#consent--persistence) | +| Contentful fetching and entry resolution | Required for first integration | Establish the app-owned fetch and the imperative resolver. | App owns the CDA fetch (no managed path); single-locale and include depth; synchronous fail-soft resolve; baseline fallback. | [render](../../internal/sdk-knowledge/native/ios.md#render--entry-resolution), [fallback](../../internal/sdk-knowledge/native/ios.md#failure--fallback-behavior) | +| Screen and navigation tracking | Required for first integration | Deepen the quick-start screen proof across UIKit navigation. | `trackCurrentScreen` from `viewDidAppear`; route-key dedupe; `screen` versus `trackCurrentScreen` choice. | [events](../../internal/sdk-knowledge/native/ios.md#events--tracking) | +| Entry interaction tracking | Common but policy-dependent | Explain UIKit-owned taps and view timing. | Tap payload from stored resolution; `ViewTrackingController` timing model; app owns geometry; opt-outs and duplicate prevention. | [events](../../internal/sdk-knowledge/native/ios.md#events--tracking) | +| Identity, profile continuity, and reset | Common but policy-dependent | Integrate login/logout and profile continuity. | Identify/reset example; `UserDefaults` continuity; reset preserves consent. | [consent](../../internal/sdk-knowledge/native/ios.md#consent--persistence), [identifiers](../../internal/sdk-knowledge/native/ios.md#identifier-ownership) | +| Custom events and analytics diagnostics | Optional | Cover business events and the debug/forwarding seam. | `track` example; passthrough `eventStream` with subscribe-before-fire; `blockedEventStream`; forwarding link. | [events](../../internal/sdk-knowledge/native/ios.md#events--tracking) | +| Custom Flags and MergeTag rendering | Optional | Cover content-model-dependent reads. | `getFlag` versus `flagPublisher`; `getMergeTagValue`; flag-view exposure governance. | [events](../../internal/sdk-knowledge/native/ios.md#events--tracking), [render](../../internal/sdk-knowledge/native/ios.md#render--entry-resolution) | +| Live updates and locked variants | Optional | Let UIKit apps choose live or locked content. | Locked snapshot versus live subscription; `nil` versus explicit selections; preview forces redraw. | [render](../../internal/sdk-knowledge/native/ios.md#render--entry-resolution), [runtime](../../internal/sdk-knowledge/native/ios.md#version--runtime-quirks) | +| Preview panel | Optional | Add the UIKit debug/authoring surface. | Debug gating; `PreviewPanelViewController`; `PreviewContentfulClient` for names; same client instance. | [runtime](../../internal/sdk-knowledge/native/ios.md#version--runtime-quirks) | +| Runtime locale changes | Optional | Handle post-startup locale switches. | `setLocale` updates SDK locale only; refetch CDA and re-resolve; locale-aware cache keys. | [runtime](../../internal/sdk-knowledge/native/ios.md#version--runtime-quirks), [identifiers](../../internal/sdk-knowledge/native/ios.md#identifier-ownership) | +| Offline delivery, queue observability, and app-owned caching | Advanced or production-only | Expose production delivery posture and the app cache boundary. | Default offline path; queue policy and callbacks; in-memory 100-event cap; app-owned content caching. | [runtime](../../internal/sdk-knowledge/native/ios.md#version--runtime-quirks), [events](../../internal/sdk-knowledge/native/ios.md#events--tracking) | ## SDK-specific authoring overrides @@ -60,10 +60,10 @@ guide: ../../guides/integrating-the-optimization-ios-sdk-in-a-uikit-app.md [render](../../internal/sdk-knowledge/native/ios.md#render--entry-resolution). - Prove "init plus one accepted screen event" in the quick start, keeping the existing visible-label proof but adding a failure-branch diagnostic. Entry rendering moves to Core because iOS has no - managed fetch. Facts: [setup](../../internal/sdk-knowledge/native/ios.md#setup--factory), + managed fetch. Facts: [setup](../../internal/sdk-knowledge/native/ios.md#setup--initialization-and-binding), [events](../../internal/sdk-knowledge/native/ios.md#events--tracking). - Show the `SceneDelegate` runtime root as a `+`/`-` diff, not a full paste-over: it is a file every - app already owns. Facts: [setup](../../internal/sdk-knowledge/native/ios.md#setup--factory). + app already owns. Facts: [setup](../../internal/sdk-knowledge/native/ios.md#setup--initialization-and-binding). - Split screen, entry-interaction, and custom-event tracking into distinct sections rather than one multi-feature section, so each is its own reader goal with its own category. Facts: [events](../../internal/sdk-knowledge/native/ios.md#events--tracking). diff --git a/documentation/authoring/blueprints/nextjs-app-router.md b/documentation/authoring/blueprints/nextjs-app-router.md index 63fa42b8c..f984cd4c8 100644 --- a/documentation/authoring/blueprints/nextjs-app-router.md +++ b/documentation/authoring/blueprints/nextjs-app-router.md @@ -15,14 +15,14 @@ guide: ../../guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app. confirm the same text remains after hydration. - **Reader shape:** An App Router application that already fetches a page and hands its entries to app-owned components. -- **Required artifacts:** Package install and browser-visible configuration; one bound factory; the +- **Required artifacts:** Package install and browser-visible configuration; one binding module; the version-appropriate request handler; representative root-layout and entry-renderer diffs; the page tracker; the Contentful fetch constraints needed for resolution; and a performable check of server HTML and hydration. - **Deliberate simplifications:** Consent is granted on server and browser only for the first proof; the guide must point to the production consent section. - **Explainer switches:** profile point = omit; managed-fetch clause = include. -- **Fact sources:** [setup and factory](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--factory), +- **Fact sources:** [setup and binding](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [identifiers](../../internal/sdk-knowledge/web/nextjs-app-router.md#identifier-ownership), [events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking), @@ -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 binding module and server/client entry points before deeper configuration. | Runtime-specific entry points, the single-binding 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--initialization-and-binding) | +| 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--initialization-and-binding), [entry-source boundary](../../internal/sdk-knowledge/shared/concepts.md#entry-source-boundary-managed-or-manual), [entry resolution](../../internal/sdk-knowledge/shared/concepts.md#entry-resolution) | +| Request context and the profile cookie | Common but policy-dependent | Teach how 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--initialization-and-binding), [runtime quirks](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks), [fallback](../../internal/sdk-knowledge/shared/concepts.md#baseline-fallback) | +| The bound root and page events | Required for first integration | Explain 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--initialization-and-binding), [components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [React Web setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding), [React Web runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks), [live updates](../../internal/sdk-knowledge/shared/concepts.md#live-updates) | +| Entry interaction tracking | Common but policy-dependent | Make default interaction instrumentation and policy controls visible. | Tracked interactions, attribution, binding and per-entry controls, consent/profile consequences, and one performable check. | [App Router events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking), [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [shared consent](../../internal/sdk-knowledge/shared/concepts.md#consent--persistence) | +| Consent, identity, profile, and reset | Common but policy-dependent | Replace the quick-start consent shortcut with application-owned policy and identity flows. | A production consent flow shared by server and browser, identity/reset controls, and clear SDK-versus-application cleanup ownership. | [App Router consent](../../internal/sdk-knowledge/web/nextjs-app-router.md#consent--persistence), [identifiers](../../internal/sdk-knowledge/web/nextjs-app-router.md#identifier-ownership), [React Web consent/actions](../../internal/sdk-knowledge/web/react-web.md#consent--persistence), [Web failure behavior](../../internal/sdk-knowledge/web/web.md#failure--fallback-behavior) | +| Analytics forwarding | Optional | Teach the safe browser forwarding seam, then route vendor-specific setup to the supplemental guide. | A lifecycle-safe subscription with consent gating, deduplication and failure handling, plus the supplemental-guide route. | [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [React Web consent](../../internal/sdk-knowledge/web/react-web.md#consent--persistence), [Web consent/observables](../../internal/sdk-knowledge/web/web.md#consent--persistence) | +| Merge tags and Custom Flags | Optional | Cover content-model-dependent personalization beyond entry replacement. | One complete Rich Text merge-tag example, the alternate hook route, and one observable Custom Flag example with authoring links. | [App Router components](../../internal/sdk-knowledge/web/nextjs-app-router.md#components--hooks), [React Web rendering](../../internal/sdk-knowledge/web/react-web.md#render--entry-resolution), [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [shared entry resolution](../../internal/sdk-knowledge/shared/concepts.md#entry-resolution) | +| Preview panel | Optional | Provide an environment-gated authoring and debugging path. | Installation and attach flow, readiness/environment/error handling, browser-safety considerations, and a forced-variant check. | [React Web runtime quirks](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks), [Web fallback/preview](../../internal/sdk-knowledge/web/web.md#failure--fallback-behavior) | +| Route-level SSR, browser takeover, and browser-owned islands | Advanced or production-only | Help mature applications choose ownership per route. | A decision table comparing first-paint, browser, event, cache ownership, and tradeoffs; 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..f59eed2d5 100644 --- a/documentation/authoring/blueprints/nextjs-pages-router.md +++ b/documentation/authoring/blueprints/nextjs-pages-router.md @@ -15,11 +15,11 @@ guide: ../../guides/integrating-the-optimization-sdk-in-a-nextjs-pages-router-ap after hydration. - **Reader shape:** A Pages Router page whose `getServerSideProps` fetches entries and passes them to app-owned components. -- **Required artifacts:** Package install; browser factory; server helper; `_app.tsx` diff; +- **Required artifacts:** Package install; browser binding; server helper; `_app.tsx` diff; `getServerSideProps` diff; entry-renderer diff; verification. - **Deliberate simplifications:** Consent is granted for the first proof. - **Explainer switches:** profile point = omit; managed-fetch clause = include. -- **Fact sources:** [setup](../../internal/sdk-knowledge/web/nextjs-pages-router.md#setup--factory), +- **Fact sources:** [setup](../../internal/sdk-knowledge/web/nextjs-pages-router.md#setup--initialization-and-binding), [rendering](../../internal/sdk-knowledge/web/nextjs-pages-router.md#render--entry-resolution), [fallback](../../internal/sdk-knowledge/web/nextjs-pages-router.md#failure--fallback-behavior). @@ -27,35 +27,38 @@ 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; client and server binding modules; bridge from quick start. | [package and setup](../../internal/sdk-knowledge/web/nextjs-pages-router.md#package--entry-points) | +| Fetching Contentful entries | Required for first integration | Establish manual and managed server entry sources. | 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--initialization-and-binding), [identifiers](../../internal/sdk-knowledge/web/nextjs-pages-router.md#identifier-ownership) | +| The bound root and page events | Required for first integration | Apply serialized state before children and assign first-event ownership. | `_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 - State plainly that Pages Router server work happens in `getServerSideProps`, not middleware or a 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). + Facts: [setup](../../internal/sdk-knowledge/web/nextjs-pages-router.md#setup--initialization-and-binding). +- 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/authoring/blueprints/node.md b/documentation/authoring/blueprints/node.md index 7540c2a0d..e2a6c140d 100644 --- a/documentation/authoring/blueprints/node.md +++ b/documentation/authoring/blueprints/node.md @@ -17,7 +17,7 @@ guide: ../../guides/integrating-the-node-sdk-in-a-node-app.md request; the `curl` verification. - **Deliberate simplifications:** Consent is granted per request and profile persistence is deferred. - **Explainer switches:** profile point = include; managed-fetch clause = include. -- **Fact sources:** [setup](../../internal/sdk-knowledge/node/node.md#setup--factory), +- **Fact sources:** [setup](../../internal/sdk-knowledge/node/node.md#setup--initialization-and-binding), [events](../../internal/sdk-knowledge/node/node.md#events--tracking), [runtime](../../internal/sdk-knowledge/node/node.md#version--runtime-quirks). @@ -33,7 +33,7 @@ guide: ../../guides/integrating-the-node-sdk-in-a-node-app.md | Section | Category | Purpose | Must teach or show | Fact sources | | ------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Install and initialize the Node SDK | Required for first integration | Establish the process-level instance before request work. | Complete initialization module; environment ownership; one-instance rule. | [setup](../../internal/sdk-knowledge/node/node.md#setup--factory) | +| Install and initialize the Node SDK | Required for first integration | Establish the process-level instance before request work. | Complete initialization module; environment ownership; one-instance rule. | [setup](../../internal/sdk-knowledge/node/node.md#setup--initialization-and-binding) | | Bind request context and locale | Required for first integration | Teach the request-scoped inputs every later call consumes. | Request-to-context adapter; locale precedence; ownership statement. | [events](../../internal/sdk-knowledge/node/node.md#events--tracking), [runtime](../../internal/sdk-knowledge/node/node.md#version--runtime-quirks) | | Apply consent policy | Common but policy-dependent | Replace the quick-start shortcut with application policy. | Boolean and split-axis forms; blocked result; app-owned policy seam. | [consent](../../internal/sdk-knowledge/node/node.md#consent--persistence) | | Evaluate route requests with `page()` | Required for first integration | Explain the main request evaluation and its result envelope. | Request example; result-field glossary; accepted-versus-blocked branch. | [events](../../internal/sdk-knowledge/node/node.md#events--tracking) | diff --git a/documentation/authoring/blueprints/react-native.md b/documentation/authoring/blueprints/react-native.md index d3e190813..8920b30ec 100644 --- a/documentation/authoring/blueprints/react-native.md +++ b/documentation/authoring/blueprints/react-native.md @@ -18,7 +18,7 @@ guide: ../../guides/integrating-the-react-native-sdk-in-a-react-native-app.md screen/entry example; visible event diagnostics; verification. - **Deliberate simplifications:** Consent starts granted and managed fetching leads. - **Explainer switches:** profile point = include; managed-fetch clause = include. -- **Fact sources:** [setup](../../internal/sdk-knowledge/native/react-native.md#setup--factory), +- **Fact sources:** [setup](../../internal/sdk-knowledge/native/react-native.md#setup--initialization-and-binding), [rendering](../../internal/sdk-knowledge/native/react-native.md#render--entry-resolution), [events](../../internal/sdk-knowledge/native/react-native.md#events--tracking). @@ -34,7 +34,7 @@ guide: ../../guides/integrating-the-react-native-sdk-in-a-react-native-app.md | Section | Category | Purpose | Must teach or show | Fact sources | | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Install and initialize `OptimizationRoot` | Required for first integration | Establish the native provider tree and async initialization. | Install/build commands; root diff; readiness behavior; one-instance rule. | [setup](../../internal/sdk-knowledge/native/react-native.md#setup--factory) | +| Install and initialize `OptimizationRoot` | Required for first integration | Establish the native provider tree and async initialization. | Install/build commands; root diff; readiness behavior; single-runtime rule. | [setup](../../internal/sdk-knowledge/native/react-native.md#setup--initialization-and-binding) | | Consent and privacy-policy handoff | Common but policy-dependent | Replace the quick-start shortcut and explain native persistence. | Two-axis example; pre-consent events; app-owned policy. | [consent](../../internal/sdk-knowledge/native/react-native.md#consent--persistence) | | Contentful entry fetching and locale shape | Required for first integration | Establish manual and managed entry sources. | Both paths; locale/include requirements; client ownership. | [rendering](../../internal/sdk-knowledge/native/react-native.md#render--entry-resolution) | | Entry resolution and fallback rendering | Required for first integration | Complete the entry half of Milestone 1. | Render-prop example and cast; static-child option; loading/error/fallback distinction. | [rendering](../../internal/sdk-knowledge/native/react-native.md#render--entry-resolution), [fallback](../../internal/sdk-knowledge/native/react-native.md#failure--fallback-behavior) | @@ -46,7 +46,7 @@ guide: ../../guides/integrating-the-react-native-sdk-in-a-react-native-app.md | Preview panel | Optional | Add the native authoring/debug surface. | Peer installs; custom-dev-build requirement; overlay placement. | [runtime](../../internal/sdk-knowledge/native/react-native.md#version--runtime-quirks) | | Offline event delivery | Optional | Explain constrained-network delivery. | NetInfo setup; in-memory queue boundary; restart caveat. | [events](../../internal/sdk-knowledge/native/react-native.md#events--tracking) | | Analytics forwarding | Optional | Hand off to the supplemental workflow. | State subscription seam; guide link. | [events](../../internal/sdk-knowledge/native/react-native.md#events--tracking) | -| Explicit SDK instance ownership | Advanced or production-only | Support adapters and tests that must construct before React. | Create/destroy lifecycle and single-instance rule. | [setup](../../internal/sdk-knowledge/native/react-native.md#setup--factory) | +| Explicit SDK runtime ownership | Advanced or production-only | Support adapters and tests that must initialize before React. | Initialize/destroy lifecycle and single-runtime rule. | [setup](../../internal/sdk-knowledge/native/react-native.md#setup--initialization-and-binding) | | Strict event admission and queue controls | Advanced or production-only | Expose production privacy and delivery posture. | Empty allow-list; blocked callback; queue policy. | [consent](../../internal/sdk-knowledge/native/react-native.md#consent--persistence) | | Platform build boundaries | Advanced or production-only | Collect native-only build and host constraints. | Expo/bare distinction; emulator host override; release validation. | [runtime](../../internal/sdk-knowledge/native/react-native.md#version--runtime-quirks) | @@ -55,7 +55,7 @@ guide: ../../guides/integrating-the-react-native-sdk-in-a-react-native-app.md - Use screen/tap/viewport language throughout; never import page/click/hover vocabulary from web. Facts: [events](../../internal/sdk-knowledge/native/react-native.md#events--tracking). - Keep the native build step in the quick start, even though deeper platform constraints belong in - Advanced. Facts: [setup](../../internal/sdk-knowledge/native/react-native.md#setup--factory). + Advanced. Facts: [setup](../../internal/sdk-knowledge/native/react-native.md#setup--initialization-and-binding). ## Troubleshooting scope diff --git a/documentation/authoring/blueprints/react-web.md b/documentation/authoring/blueprints/react-web.md index b4349e892..6e7b2cbc6 100644 --- a/documentation/authoring/blueprints/react-web.md +++ b/documentation/authoring/blueprints/react-web.md @@ -19,7 +19,7 @@ guide: ../../guides/integrating-the-react-web-sdk-in-a-react-app.md loading/error handling; verification. - **Deliberate simplifications:** Consent starts granted; real policy is deferred. - **Explainer switches:** profile point = omit; managed-fetch clause = include. -- **Fact sources:** [setup](../../internal/sdk-knowledge/web/react-web.md#setup--factory), +- **Fact sources:** [setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding), [rendering](../../internal/sdk-knowledge/web/react-web.md#render--entry-resolution), [fallback](../../internal/sdk-knowledge/web/react-web.md#failure--fallback-behavior). @@ -34,7 +34,7 @@ guide: ../../guides/integrating-the-react-web-sdk-in-a-react-app.md | Section | Category | Purpose | Must teach or show | Fact sources | | ---------------------------------------------- | ------------------------------ | --------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| How the SDK fits your app | Required for first integration | Establish the single root and provider composition. | Root diff; config ownership; one-mount rule. | [setup](../../internal/sdk-knowledge/web/react-web.md#setup--factory) | +| How the SDK fits your app | Required for first integration | Establish the single root and provider composition. | Root diff; config ownership; one-mount rule. | [setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding) | | SDK readiness, loading, and error states | Required for first integration | Make the asynchronous browser lifecycle explicit. | Readiness timeline; loading/error UI; baseline-reveal behavior. | [runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks), [fallback](../../internal/sdk-knowledge/web/react-web.md#failure--fallback-behavior) | | Fetching Contentful entries | Required for first integration | Explain manual and managed entry sources. | Both paths; client ownership; locale/include requirements. | [rendering](../../internal/sdk-knowledge/web/react-web.md#render--entry-resolution) | | Resolving entries and rendering the result | Required for first integration | Complete Milestone 1 at the component boundary. | Render-prop diff and definition; cast; host-element/double-wrap rules; fallback. | [rendering](../../internal/sdk-knowledge/web/react-web.md#render--entry-resolution) | @@ -61,7 +61,7 @@ guide: ../../guides/integrating-the-react-web-sdk-in-a-react-app.md | ------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Entry stays on baseline, loading, or error fallback | Distinguishes three common states. | [fallback](../../internal/sdk-knowledge/web/react-web.md#failure--fallback-behavior) | | Render-prop type error or wrapped entry renders nothing | Common component-boundary mistakes. | [rendering](../../internal/sdk-knowledge/web/react-web.md#render--entry-resolution) | -| Hook is outside provider or SDK initializes twice | Common provider-tree mistakes. | [setup](../../internal/sdk-knowledge/web/react-web.md#setup--factory) | +| Hook is outside provider or SDK initializes twice | Common provider-tree mistakes. | [setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding) | | Page/interaction events or live updates are missing | Makes tracker, consent, metadata, and opt-in live behavior actionable. | [events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks) | | Preview panel does not attach | Common readiness/package boundary. | [runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks) | diff --git a/documentation/authoring/blueprints/web.md b/documentation/authoring/blueprints/web.md index 5027016d5..fbffa8c37 100644 --- a/documentation/authoring/blueprints/web.md +++ b/documentation/authoring/blueprints/web.md @@ -16,7 +16,7 @@ guide: ../../guides/integrating-the-web-sdk-in-a-web-app.md resolve-render example; the verification step. - **Deliberate simplifications:** Consent starts granted; production gating is deferred. - **Explainer switches:** profile point = omit; managed-fetch clause = include. -- **Fact sources:** [setup](../../internal/sdk-knowledge/web/web.md#setup--factory), +- **Fact sources:** [setup](../../internal/sdk-knowledge/web/web.md#setup--initialization-and-binding), [rendering](../../internal/sdk-knowledge/web/web.md#render--entry-resolution), [fallback](../../internal/sdk-knowledge/web/web.md#failure--fallback-behavior). @@ -29,23 +29,23 @@ guide: ../../guides/integrating-the-web-sdk-in-a-web-app.md ## Section map -| Section | Category | Purpose | Must teach or show | Fact sources | -| ----------------------------------------------------- | ------------------------------ | ----------------------------------------------------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | -| How the SDK fits your app | Required for first integration | Establish the single stateful browser instance. | Complete reusable singleton; config ownership; duplicate-instance warning. | [setup](../../internal/sdk-knowledge/web/web.md#setup--factory) | -| The SDK lifecycle: create, emit, resolve | Required for first integration | Teach the ordering that prevents silent baseline output. | Lifecycle diagram or ordered example; accepted-event checkpoint. | [runtime](../../internal/sdk-knowledge/web/web.md#version--runtime-quirks) | -| Fetching Contentful entries | Required for first integration | Explain manual and managed entry sources. | Both paths; locale/include requirements; client ownership. | [rendering](../../internal/sdk-knowledge/web/web.md#render--entry-resolution) | -| Resolving entries and rendering the result | Required for first integration | Complete Milestone 1. | Resolve/render example; result fields; baseline ID distinction and fallback. | [rendering](../../internal/sdk-knowledge/web/web.md#render--entry-resolution) | -| Page and route events | Required for first integration | Maintain selections across initial load and SPA navigation. | Initial page example; stable route-key dedupe; hybrid skip note. | [events](../../internal/sdk-knowledge/web/web.md#events--tracking) | -| Consent and privacy handoff | Common but policy-dependent | Replace the quick-start consent shortcut. | Consent state transitions; app-owned record; denied behavior. | [consent](../../internal/sdk-knowledge/web/web.md#consent--persistence) | -| State subscriptions, locale changes, and re-rendering | Common but policy-dependent | Implement Milestone 2 for an imperative runtime. | Subscription cleanup; re-resolve trigger; locale ownership. | [runtime](../../internal/sdk-knowledge/web/web.md#version--runtime-quirks) | -| Entry interaction tracking | Common but policy-dependent | Explain browser view/click/hover instrumentation. | Defaults; opt-outs; clickable-path override. | [events](../../internal/sdk-knowledge/web/web.md#events--tracking) | -| Identity, profile, and reset | Common but policy-dependent | Integrate login and logout with browser state. | Identify/reset example and consent distinction. | [consent](../../internal/sdk-knowledge/web/web.md#consent--persistence) | -| Web Components entry rendering | Optional | Offer the declarative alternative to the class API. | Registration and element example; manual-versus-managed inputs. | [components](../../internal/sdk-knowledge/web/web.md#components--hooks) | -| Merge tags and Custom Flags | Optional | Cover content-model-dependent reads. | Merge-tag and flag examples with event behavior. | [rendering](../../internal/sdk-knowledge/web/web.md#render--entry-resolution) | -| Analytics forwarding | Optional | Hand off to the supplemental workflow. | Subscription seam and supplemental-guide link. | [events](../../internal/sdk-knowledge/web/web.md#events--tracking) | -| Preview panel | Optional | Add environment-gated authoring tooling. | Separate package; attach example; required input. | [runtime](../../internal/sdk-knowledge/web/web.md#version--runtime-quirks) | -| Hybrid Node SSR and browser continuity | Advanced or production-only | Preserve profile and event ownership across runtimes. | Cookie and first-event contract; reference link. | [identifiers](../../internal/sdk-knowledge/web/web.md#identifier-ownership) | -| Strict consent, storage, and delivery controls | Advanced or production-only | Expose production privacy and delivery posture. | Empty allow-list; storage/queue settings; blocked diagnostics. | [consent](../../internal/sdk-knowledge/web/web.md#consent--persistence) | +| Section | Category | Purpose | Must teach or show | Fact sources | +| ----------------------------------------------------- | ------------------------------ | ----------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| How the SDK fits your app | Required for first integration | Establish the single stateful browser instance. | Complete reusable singleton; config ownership; duplicate-instance warning. | [setup](../../internal/sdk-knowledge/web/web.md#setup--initialization-and-binding) | +| The SDK lifecycle: create, emit, resolve | Required for first integration | Teach the ordering that prevents silent baseline output. | Lifecycle diagram or ordered example; accepted-event checkpoint. | [runtime](../../internal/sdk-knowledge/web/web.md#version--runtime-quirks) | +| Fetching Contentful entries | Required for first integration | Explain manual and managed entry sources. | Both paths; locale/include requirements; client ownership. | [rendering](../../internal/sdk-knowledge/web/web.md#render--entry-resolution) | +| Resolving entries and rendering the result | Required for first integration | Complete Milestone 1. | Resolve/render example; result fields; baseline ID distinction and fallback. | [rendering](../../internal/sdk-knowledge/web/web.md#render--entry-resolution) | +| Page and route events | Required for first integration | Maintain selections across initial load and SPA navigation. | Initial page example; stable route-key dedupe; hybrid skip note. | [events](../../internal/sdk-knowledge/web/web.md#events--tracking) | +| Consent and privacy handoff | Common but policy-dependent | Replace the quick-start consent shortcut. | Consent state transitions; app-owned record; denied behavior. | [consent](../../internal/sdk-knowledge/web/web.md#consent--persistence) | +| State subscriptions, locale changes, and re-rendering | Common but policy-dependent | Implement Milestone 2 for an imperative runtime. | Subscription cleanup; re-resolve trigger; locale ownership. | [runtime](../../internal/sdk-knowledge/web/web.md#version--runtime-quirks) | +| Entry interaction tracking | Common but policy-dependent | Explain browser view/click/hover instrumentation. | Defaults; opt-outs; clickable-path override. | [events](../../internal/sdk-knowledge/web/web.md#events--tracking) | +| Identity, profile, and reset | Common but policy-dependent | Integrate login and logout with browser state. | Identify/reset example and consent distinction. | [consent](../../internal/sdk-knowledge/web/web.md#consent--persistence) | +| Web Components entry rendering | Optional | Offer the declarative alternative to the class API. | Registration and element example; manual-versus-managed inputs. | [components](../../internal/sdk-knowledge/web/web.md#components--hooks) | +| Merge tags and Custom Flags | Optional | Cover content-model-dependent reads. | Merge-tag and flag examples with event behavior. | [rendering](../../internal/sdk-knowledge/web/web.md#render--entry-resolution) | +| Analytics forwarding | Optional | Hand off to the supplemental workflow. | Subscription seam and supplemental-guide link. | [events](../../internal/sdk-knowledge/web/web.md#events--tracking) | +| Preview panel | Optional | Add environment-gated authoring tooling. | Separate package; attach example; required input. | [runtime](../../internal/sdk-knowledge/web/web.md#version--runtime-quirks) | +| Hybrid Node SSR and browser continuity | Advanced or production-only | Preserve profile and event ownership across runtimes. | Cookie and first-event contract; reference link. | [identifiers](../../internal/sdk-knowledge/web/web.md#identifier-ownership) | +| Strict consent, storage, and delivery controls | Advanced or production-only | Expose production privacy and delivery posture. | Empty allow-list; storage/queue settings; blocked diagnostics. | [consent](../../internal/sdk-knowledge/web/web.md#consent--persistence) | ## SDK-specific authoring overrides @@ -57,7 +57,7 @@ guide: ../../guides/integrating-the-web-sdk-in-a-web-app.md | Reader symptom | Why it belongs | Fact sources | | ----------------------------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | Entry remains on baseline | Most common first-run symptom. | [fallback](../../internal/sdk-knowledge/web/web.md#failure--fallback-behavior) | -| SDK initializes twice | Common SPA singleton mistake. | [setup](../../internal/sdk-knowledge/web/web.md#setup--factory) | +| SDK initializes twice | Common SPA singleton mistake. | [setup](../../internal/sdk-knowledge/web/web.md#setup--initialization-and-binding) | | SPA page or interaction events are missing/duplicated | Makes route keys, consent, and DOM metadata actionable. | [events](../../internal/sdk-knowledge/web/web.md#events--tracking) | | Custom Flag or hybrid profile behavior is unexpected | Covers dedupe and cross-runtime identity. | [events](../../internal/sdk-knowledge/web/web.md#events--tracking), [identifiers](../../internal/sdk-knowledge/web/web.md#identifier-ownership) | 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..3c73b87c6 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 bindings 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,28 +179,28 @@ 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` binding and put +the request policy in `consent.server`. For Pages Router `getServerSideProps`, use the +`/pages-router/server` binding and put the same policy in `consent.server`: Next.js App Router / TypeScript: ```ts -createNextjsAppRouterOptimization({ +bindNextjsAppRouterOptimization({ 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 binding. 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/entry-personalization-and-variant-resolution.md b/documentation/concepts/entry-personalization-and-variant-resolution.md index 3affd0dc6..8e1098795 100644 --- a/documentation/concepts/entry-personalization-and-variant-resolution.md +++ b/documentation/concepts/entry-personalization-and-variant-resolution.md @@ -92,10 +92,10 @@ the runtime: | Android | `suspend OptimizationClient.resolveOptimizedEntry(...)` | Compose `OptimizedEntry`; XML Views `OptimizedEntryView` | For Next.js App Router integrations, prefer the app-local bound `OptimizedEntry` returned by -`createNextjsAppRouterOptimization()` from `@contentful/optimization-nextjs/app-router`. In Server +`bindNextjsAppRouterOptimization()` from `@contentful/optimization-nextjs/app-router`. In Server Components it resolves through the Node SDK and server data; in Client Components the same app-local name resolves through React Web. Pages Router integrations use -`createNextjsPagesRouterOptimization()` from `@contentful/optimization-nextjs/pages-router` for +`bindNextjsPagesRouterOptimization()` from `@contentful/optimization-nextjs/pages-router` for client rendering, and routes that resolve entries manually can call `getServerTrackingAttributes()` or `ServerOptimizedEntry` when they need SSR tracking attributes. `ServerOptimizedEntry` accepts either manual `baselineEntry` and `resolvedData` props or the result returned by managed 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..870a12c16 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. @@ -325,7 +325,7 @@ of tracking that can only be measured in the browser. Use SDK wrappers or helpers when available instead of copying the attribute map into application code. In Next.js App Router integrations, the preferred wrapper is the app-local bound -`OptimizedEntry` returned by `createNextjsAppRouterOptimization()`. In Server Components, it +`OptimizedEntry` returned by `bindNextjsAppRouterOptimization()`. In Server Components, it resolves the baseline entry, renders the server-selected entry, and emits the Web SDK tracking attributes through server internals. For custom SSR wrappers, call `getServerTrackingAttributes()` from `@contentful/optimization-nextjs/tracking-attributes`. Non-Next runtimes can call @@ -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. @@ -447,7 +447,7 @@ remains server-owned. The [Next.js SDK App Router reference implementation](../../implementations/nextjs-sdk_app-router/README.md) is one concrete example of the same server-to-browser tracking pattern. In Next.js App Router, -prefer the `@contentful/optimization-nextjs/app-router` factory so app code imports app-local bound +prefer the `@contentful/optimization-nextjs/app-router` binding so app code imports app-local bound `OptimizationRoot`, `OptimizationProvider`, `OptimizedEntry`, and route trackers from one binding module. Use `/pages-router` for Pages Router client components and `/pages-router/server` for `getServerSideProps`. Use adapter subpaths for manual server, tracking-attribute, request, or client @@ -459,7 +459,7 @@ Keep personalization server-owned by enforcing these boundaries: - Next.js App Router Server Components can import the app-local bound `OptimizationRoot`, `OptimizationProvider`, `OptimizedEntry`, and route trackers from the binding module created by - `createNextjsAppRouterOptimization()`. The bound server root or provider owns request data and + `bindNextjsAppRouterOptimization()`. The bound server root or provider owns request data and profile handoff, and the bound server `OptimizedEntry` resolves and renders tracked entries. - Lower-level Next.js server modules can still import `/server` helpers, call the request-bound page path, call `sdk.resolveOptimizedEntry(...)`, and render attributes from 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..bb1dee5b4 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 `bindNextjsAppRouterOptimization({ locale })` bound components and `createRequestHandoff({ locale })`; Pages Router `/pages-router/server` `createRequestHandoff(context, { locale })`; lower-level server `configureNextjsServerOptimization({ 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.initialize({ 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 @@ -183,8 +183,8 @@ const entry = await contentfulClient.getEntry(entryId, { ## Next.js adapter Next.js composes the stateless Node SDK on the server with the React Web SDK on the client. For App -Router integrations, prefer the `/app-router` `createNextjsAppRouterOptimization({ locale })` -factory. It returns app-local bound `OptimizationRoot`, `OptimizationProvider`, `OptimizedEntry`, +Router integrations, prefer the `/app-router` `bindNextjsAppRouterOptimization({ locale })` +binding. It returns app-local bound `OptimizationRoot`, `OptimizationProvider`, `OptimizedEntry`, and route trackers. Next.js resolves those bound exports to the automatic server implementation in Server Components and to React Web-backed client exports in Client Components. @@ -192,31 +192,28 @@ Next.js App Router binding module (TypeScript): ```ts export const { NextAppAutoPageTracker, OptimizationRoot, OptimizedEntry } = - createNextjsAppRouterOptimization({ + bindNextjsAppRouterOptimization({ clientId, locale: appLocale, - server: { - enabled: true, - consent, - }, + consent: { server: consent }, }) ``` -The bound server root and bound server `OptimizedEntry` use the factory `locale` when they load +The bound server root and bound server `OptimizedEntry` use the binding `locale` when they load server Optimization data. The bound client root applies the same locale through React Web configuration. -Keep lower-level/manual surfaces for direct request control. `createNextjsOptimization({ locale })` +Keep lower-level/manual surfaces for direct request control. `configureNextjsServerOptimization({ 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 binding'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): ```ts -const optimization = createNextjsOptimization({ +const optimization = configureNextjsServerOptimization({ clientId, locale: defaultLocale, }) @@ -232,17 +229,18 @@ export const proxy = createNextjsOptimizationContextHandler() ``` Use the request-scoped manual `locale` path when a manual route can serve different locales. A -module-level `createNextjsOptimization({ locale })` value is a default for the server SDK instance, +module-level `configureNextjsServerOptimization({ locale })` value is a default for the server SDK runtime, 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 @@ -293,8 +291,8 @@ Pass direct single-locale field values to the runtime-specific entry resolution `entryId` plus optional `entryQuery`. - Web Component `ctfl-optimized-entry` with either `baselineEntry` or managed `entry-id`/`entryId` plus optional `entryQuery`. -- Next.js bound `OptimizedEntry` from `createNextjsAppRouterOptimization()` for App Router Server - and Client Components, or from `createNextjsPagesRouterOptimization()` for Pages Router client +- Next.js bound `OptimizedEntry` from `bindNextjsAppRouterOptimization()` for App Router Server + and Client Components, or from `bindNextjsPagesRouterOptimization()` for Pages Router client rendering. Lower-level server flows can use `resolveOptimizedEntry()` or managed `fetchOptimizedEntry()`, then pass manual `baselineEntry` and `resolvedData` props or the managed result to `ServerOptimizedEntry` when server-rendered tracking attributes are needed. @@ -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 })`. + `bindNextjsAppRouterOptimization({ 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..f7a0b8ea0 --- /dev/null +++ b/documentation/concepts/optimization-handoff-and-cache-safe-rendering.md @@ -0,0 +1,188 @@ +--- +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` | Binds request handoff helpers, customer-owned selection handoff helpers, roots, analytics roots, and server tracking attributes for App Router routes. | +| `@contentful/optimization-nextjs/pages-router` and `/pages-router/server` | Binds Pages Router roots, `getServerSideProps` request handoff helpers, and customer-owned selection handoff helpers. | +| `@contentful/optimization-nextjs/edge` | Configures Edge runtime request handoff and customer-owned selection handoff helpers. | +| `@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 generic cache-safety helper reports warnings rather than blocking rendering. Request handoff +creation is stricter: request-derived profile state must use `private-request` cache scope. Creating +a request handoff with profile state and `public-permutation` or `static` cache metadata throws a +`TypeError`. The helper also warns 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 use app-owned selections, +not 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..ca70578fa 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 `bindNextjsAppRouterOptimization()`, 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..9dd279f85 100644 --- a/documentation/guides/choosing-the-right-sdk.md +++ b/documentation/guides/choosing-the-right-sdk.md @@ -23,16 +23,23 @@ tooling, and platform defaults. Use lower-level packages only when you are build tooling, tests, or first-party integrations that need shared SDK primitives or raw API access. 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 +use `@contentful/optimization-nextjs/app-router`; it provides +`bindNextjsAppRouterOptimization(...)`, which returns app-local configured 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 +71,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 +111,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..1eae4430a 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 @@ -250,26 +250,26 @@ Native integrations, prefer `onStatesReady` on the provider root so the subscrip child effects can emit SDK events. For Next.js App Router integrations, configure `onStatesReady` once in -`createNextjsAppRouterOptimization()` from `@contentful/optimization-nextjs/app-router`. The bound -`OptimizationRoot` uses that factory config and renders without per-render `clientId`, -`environment`, or `onStatesReady` props. Use lower-level `/client` root props only for manual -server/client escape hatches. +`bindNextjsAppRouterOptimization(...)` from `@contentful/optimization-nextjs/app-router`. The bound +`OptimizationRoot` uses that binding config and renders without per-render `clientId`, +`environment`, or `onStatesReady` props. The binding call is not an isolation context; call it once +for the app-local helper set. Use lower-level `/client` root props only for manual 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`. +`bindNextjsPagesRouterOptimization(...)` from `@contentful/optimization-nextjs/pages-router`, then +pass `pageProps.contentfulOptimization.handoff` to the bound root in `pages/_app.tsx`. **Adapt this to your use case:** ```tsx -import { createNextjsAppRouterOptimization } from '@contentful/optimization-nextjs/app-router' +import { bindNextjsAppRouterOptimization } from '@contentful/optimization-nextjs/app-router' const forwardedMessageIds = new Set() -export const { proxy, NextAppAutoPageTracker, OptimizationRoot, OptimizedEntry } = - createNextjsAppRouterOptimization({ - // ...clientId, environment, locale, server, defaults +export const { NextAppAutoPageTracker, OptimizationRoot, OptimizedEntry } = + bindNextjsAppRouterOptimization({ + // ...clientId, environment, locale, consent onStatesReady: (states) => { const initialMessageId = states.eventStream.current?.messageId @@ -321,15 +321,17 @@ 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 -`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 +event. App Router integrations use `createRequestHandoff()` from the app-local bound helper set 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 +`configureNextjsServerOptimization(...)` only when you intentionally configure a lower-level/manual +stateless `/server` runtime. That configuration is not a request-isolation context; bind each request +with the request helpers before reading request-local data. 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..c91367157 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,128 @@ 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 binds app-local +configured 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. Bind one app-local Optimization module. This binding shares one configured helper set for the + app; it is not a per-route or per-request isolation context. + `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 + ```tsx + // lib/optimization.ts + import { bindNextjsAppRouterOptimization } from '@contentful/optimization-nextjs/app-router' + import { contentfulClient } from './contentful' + + export const { + NextAppAutoPageTracker, + OptimizationRoot, + OptimizedEntry, + createRequestHandoff, + createHandoffFromSelections, + createOptimizationCacheKey, + getServerTrackingAttributes, + resolveEntriesForSelections, + } = bindNextjsAppRouterOptimization({ + 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 }, + }, + }) ``` -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/`). +3. Forward the original request URL so the root layout can build a stable route key. `proxy.ts` is a + Next.js file name; `x-ctfl-opt-request-url` is an SDK-owned request-context header, so use the + exact name when forwarding the URL. - **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). + **Copy this:** ```ts - // src/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 - }) - ``` - -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. + // proxy.ts + import { NextResponse, type NextRequest } from 'next/server' - 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). + export function proxy(request: NextRequest) { + const requestHeaders = new Headers(request.headers) + requestHeaders.set('x-ctfl-opt-request-url', request.url) - **Copy this:** Next.js 16. - - ```ts - // proxy.ts — at your Next.js app root - export { proxy } from './src/lib/optimization' - - export const config = { - matcher: ['/((?!_next/static|_next/image|favicon.ico|api).*)'], + return NextResponse.next({ + request: { headers: requestHeaders }, + }) } - ``` - - **Copy this:** Next.js 15 uses the same handler, aliased to the `middleware` export that Next.js - 15 looks for. - - ```ts - // middleware.ts — at your Next.js app root - export { proxy as middleware } from './src/lib/optimization' 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 = new Headers(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} -