Skip to content

feat(nextjs): add cache-safe optimization handoff support - #379

Open
Charles Hudson (phobetron) wants to merge 1 commit into
mainfrom
NT-3708_implement-ssg-isr-esr-handoff-support
Open

feat(nextjs): add cache-safe optimization handoff support#379
Charles Hudson (phobetron) wants to merge 1 commit into
mainfrom
NT-3708_implement-ssg-isr-esr-handoff-support

Conversation

@phobetron

@phobetron Charles Hudson (phobetron) commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements explicit, cache-safe Optimization SDK handoff support for Next.js static, App Router Cache Components, Pages Router ISR, request-time, Edge runtime, and analytics-only rendering flows.

This PR adds framework-neutral handoff primitives in Core, browser handoff hydration in Web, React handoff providers and roots, canonical Next.js handoff APIs, Edge runtime helpers, a Node request handoff helper, updated reference implementations, E2E coverage, and public/internal documentation for the supported rendering modes. It also renames shared-runtime SDK setup APIs away from factory-style create* names so API names match the single-runtime architecture.

Request-derived handoffs are restricted to private-request cache metadata, keeping private request state out of public or static cache paths. App-owned public permutations use helper-created cache metadata instead of ad hoc cache-key construction. Web-family custom adapters can hydrate validated handoff state through the public @contentful/optimization-web/handoff surface instead of bridge-support internals.

The implementation keeps browser hydration safe across async and cached rendering paths. Web handoff hydration suppresses stale async state publication, clears stale content state when a content-capable handoff omits changes or selectedOptimizations, marks accepted empty or undefined handoff state as successful, and keeps static/public profileless handoffs in live memory without overwriting durable LocalStore continuity, including after delayed persistence consent. React Native persistence follows the same field-presence model: omitted continuity fields are preserved and own-present undefined fields clear stored continuity.

Next.js request and middleware helpers preserve framework chainability and terminal app responses. Middleware can resolve consent, perform the server page request once, persist ctfl-opt-aid, and forward compact trusted context { consent, pageAccepted, profileId }. Existing rewrite responses are preserved while SDK request context and eligible cookie persistence are still applied; terminal redirects and non-middleware app responses remain terminal. App Router createRequestHandoff() consumes forwarded context only when trustedRequestHandoff: true is passed, refetches profile state from profileId, and uses forwarded pageAccepted to assign first-page event ownership: accepted server tracking skips the initial browser page event, while unaccepted forwarded tracking leaves the browser to emit it. Anonymous-ID denial expires domain/path-scoped cookies with matching options.

React Web validates cache safety before initial snapshot rendering can consume handoff state, keeps SSR-owned initial route skips scoped to the hydrated route including under StrictMode replay, and still emits later browser-owned route changes normally.

The docs, concepts, guides, internal SDK knowledge base, and bundle budgets describe the supported cache-safe handoff, consent, locale, profile continuity, analytics, native SDK, and cache-key behavior across Web, React Web, Next.js App Router, Next.js Pages Router, React Native, iOS, Android, and shared concepts.

Jira: NT-3708

Versioning note

Merge this PR with a non-major squash commit message so shared package releases stay on feature-level version bumps. A follow-up Next.js + React Web release-signaling PR is needed for the /esr replacement, create-style setup API removals, and React Web handoff prop replacement, so the CD system can assign major version bumps only to @contentful/optimization-nextjs and @contentful/optimization-react-web.

Why

Optimized content rendering needs to support more than browser-owned runtime state. This PR makes state handoff explicit for:

  • static routes that render known optimization selections at build time
  • App Router Cache Components routes that cache public optimization permutations
  • Pages Router ISR routes that cache public optimization permutations
  • request-time routes that hand private request state to the browser
  • Edge runtime route handlers that own request analytics and profile persistence before browser hydration
  • analytics-only browser hydration when optimized markup has already been rendered outside the browser runtime
  • custom Web-family adapters that receive a validated server/static/edge handoff and need to hydrate browser SDK state without depending on bridge-support

The implementation separates optimization selection state, cache-safety metadata, browser hydration behavior, initial page-event ownership, profile/private request state, customer-owned public permutation state, and public adapter APIs from bridge-support internals.

Review map

Suggested review order:

  1. Core model and state semantics

    • packages/universal/core-sdk/src/handoff.ts
    • packages/universal/core-sdk/src/lib/interceptor/InterceptorManager.ts
    • packages/universal/core-sdk/src/state/applyOptimizationDataToSignals.ts
    • packages/universal/core-sdk/src/preview-support/PreviewOverrideManager.ts
    • related tests
  2. Browser hydration/runtime behavior

    • packages/web/web-sdk/src/handoff.ts
    • packages/web/web-sdk/src/storage/durableContinuityPersistence.ts
    • packages/web/web-sdk/src/analytics.ts
    • packages/web/web-sdk/src/ContentfulOptimization.ts
    • related tests under packages/web/web-sdk/src/**
  3. React Web integration

    • packages/web/frameworks/react-web-sdk/src/root/OptimizationRoot.tsx
    • packages/web/frameworks/react-web-sdk/src/root/OptimizationAnalyticsRoot.tsx
    • packages/web/frameworks/react-web-sdk/src/provider/OptimizationProvider.tsx
    • packages/web/frameworks/react-web-sdk/src/auto-page/useAutoPageEmitter.ts
    • packages/web/frameworks/react-web-sdk/package.json
    • related tests under packages/web/frameworks/react-web-sdk/src/**
  4. Next.js public APIs and request/edge behavior

    • packages/web/frameworks/nextjs-sdk/src/handoff.ts
    • packages/web/frameworks/nextjs-sdk/src/app-router-server.tsx
    • packages/web/frameworks/nextjs-sdk/src/app-router-request-handoff.ts
    • packages/web/frameworks/nextjs-sdk/src/pages-router-server.ts
    • packages/web/frameworks/nextjs-sdk/src/pages-router.ts
    • packages/web/frameworks/nextjs-sdk/src/edge.ts
    • packages/web/frameworks/nextjs-sdk/src/cache-middleware.ts
    • packages/web/frameworks/nextjs-sdk/src/request-handler.ts
    • packages/web/frameworks/nextjs-sdk/src/server.tsx
  5. Reference implementations and E2E evidence

    • implementations/nextjs-sdk_app-router/**
    • implementations/nextjs-sdk_pages-router/**
    • implementations/nextjs-sdk_app-router_edge-runtime/**
    • implementations/web-sdk_angular/src/app/services/optimization.ts
    • lib/e2e-web/e2e/handoff.spec.ts
    • lib/e2e-web/e2e/offline-queue-recovery.spec.ts
    • lib/mocks/src/experience-handlers.ts
  6. Documentation and SDK knowledge

    • documentation/concepts/optimization-handoff-and-cache-safe-rendering.md
    • documentation/concepts/core-state-management.md
    • documentation/concepts/consent-management-in-the-optimization-sdk-suite.md
    • documentation/guides/rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.md
    • updated Next.js App Router, Next.js Pages Router, React Web, Web, React Native, and analytics forwarding guides
    • documentation/internal/sdk-knowledge/**

Public API changes

Core SDK

Adds framework-neutral handoff support:

  • OptimizationSelectionState
  • OptimizationCacheScope
  • private, public-permutation, and static cache metadata types
  • OptimizationHandoff
  • OptimizationCacheSafetyWarning
  • createSelectionFingerprint
  • createOptimizationCacheKey
  • createPublicPermutationCacheMetadata
  • resolveEntriesForSelections
  • createHandoffFromSelections
  • getOptimizationCacheSafetyWarnings
  • assertOptimizationCacheSafety

LifecycleInterceptors.state now accepts OptimizationSelectionState, so interceptors can handle profileless selection state produced by handoffs. Sparse interceptor returns are merged after each interceptor: omitted fields preserve the prior state visible to later interceptors, while own-present undefined fields intentionally clear state.

Web SDK

Adds browser handoff and analytics-only runtime support:

  • ContentOptimizationHandoff
  • AnalyticsOptimizationHandoff
  • BrowserOptimizationHandoff
  • hydrateOptimizationHandoff
  • hydrateOptimizationHandoffState
  • initializeOptimizationAnalyticsRuntime
  • hydrateOptimizationAnalyticsHandoff

New subpath exports:

  • @contentful/optimization-web/handoff
  • @contentful/optimization-web/analytics

hydrateOptimizationHandoff and hydrateOptimizationAnalyticsHandoff validate initialPageEvent and cache safety before publishing browser state. Handoff state hydration awaits Web SDK state interceptors, uses own-property field presence for patch semantics, and preserves profile continuity unless profile is present or intentionally cleared.

Static and public profileless handoffs hydrate the current page's changes and selectedOptimizations into live in-memory state, but they do not overwrite durable LocalStore continuity, including after delayed persistence consent. Content-capable handoffs that omit changes or selectedOptimizations clear stale content state from previous routes. Private/profile-backed handoffs keep the normal persistence path.

ContentfulOptimization.trackCurrentPage({ initialPageEvent: "skip", routeKey }) marks the supplied route key as accepted without emitting a browser page event and without requiring a page payload builder. Framework route trackers use that handoff signal only for the SSR/edge/request-owned initial mounted route, so later browser-owned SPA navigations emit normally when a payload builder is available.

React Web SDK

Adds handoff-aware provider and root support:

  • OptimizationProvider accepts handoff state, hydration behavior, and managed-entry prefetch handoff entries.
  • OptimizationProvider validates handoff cache safety before initial snapshot rendering can consume handoff state.
  • OptimizationProvider hydrates changed handoff state into an existing live runtime so persistent roots can consume new server handoffs after mount.
  • OptimizationRoot composes provider state, live updates, route identity, and initial page event payload builders.
  • OptimizationRoot can mark a skipped initial route accepted from a handoff and routeKey without buildPagePayload or initialPagePayload.
  • OptimizationAnalyticsRoot supports analytics-only hydration and limits initial-route skips to the route hydrated from that handoff.
  • React Web roots accept serializable initialPagePayload for server-framework bridges that cannot pass functions into client components.
  • Tracking attributes can be imported without pulling in the full React runtime.

New subpath exports:

  • @contentful/optimization-react-web/handoff
  • @contentful/optimization-react-web/analytics
  • @contentful/optimization-react-web/tracking-attributes

Next.js SDK

Adds canonical Next.js handoff and setup helpers:

  • createHandoffFromSelections
  • createPublicPermutationHandoff
  • createPublicPermutationCacheMetadata
  • addBrowserHandoffMetadata
  • browser handoff metadata types
  • bindNextjsAppRouterOptimization
  • bindNextjsPagesRouterOptimization
  • bindNextjsPagesRouterServerOptimization
  • configureNextjsServerOptimization
  • configureNextjsEdgeOptimization
  • createEdgeRequestHandoff
  • createNextjsPublicPermutationCacheMiddleware
  • createNextjsOptimizationContextHandler

createNextjsPublicPermutationCacheMiddleware is exported from @contentful/optimization-nextjs/cache-middleware so proxy or middleware code can use it without resolving the App Router react-server entry. It consumes public-permutation cache metadata, validates Next.js cache tags, supports default query-param rewrites, passes encoded cache keys to custom rewrites, removes stale SDK-owned x-ctfl-opt-* request overrides, and preserves existing non-SDK middleware response and request override state.

createNextjsOptimizationContextHandler() can be configured with sdk and consent to resolve consent, call the server page request once, persist ctfl-opt-aid when allowed, and forward x-ctfl-opt-server-data as compact trusted context containing consent, pageAccepted, and an optional profileId. When chained after an existing rewrite NextResponse, it preserves the rewrite while still applying SDK request context and eligible cookie persistence. It reads effective forwarded cookie headers for consent and profile binding.

App Router createRequestHandoff() consumes forwarded trusted request context only with trustedRequestHandoff: true. That opt-in should be used only for routes covered by the sanitizing request handler; raw client-supplied forwarded data is ignored otherwise. Trusted forwarded context can refetch profile state server-side from profileId, including blocked no-data results, without a duplicate page() call. It also carries the request handler's pageAccepted result so accepted server tracking skips the initial browser page event, while unaccepted forwarded tracking leaves the browser to emit it.

Pages Router request handoffs include defaults derived from resolved server consent. The bound Pages Router client components prefer those handoff defaults over static consent.clientDefaults, keeping SSR consent and hydration consent aligned.

App Router, Pages Router, Node, and Edge request handoff helpers reject public/static cache metadata before request evaluation. Public or static selections should use public permutation handoffs or non-request static handoffs instead.

Anonymous-ID persistence denial expires matching path/domain-scoped cookies with Expires and Max-Age=0, rather than relying on a bare cookie delete.

New package subpaths:

  • @contentful/optimization-nextjs/cache-middleware
  • @contentful/optimization-nextjs/edge

Removed package subpath:

  • @contentful/optimization-nextjs/esr

Node SDK

Adds createRequestHandoffFromData for framework-neutral private request handoff creation from completed server optimization data. Request handoffs are restricted to private-request cache metadata; public cache scopes should use public permutation handoffs instead.

React Native SDK

Renames manual runtime setup from ContentfulOptimization.create() to ContentfulOptimization.initialize(). The beta create() API is removed rather than preserved as a legacy alias because the React Native SDK owns one shared runtime instance per app.

React Native persisted continuity now follows own-property semantics: omitted fields preserve stored profile continuity, and own-present undefined fields clear stored profile, selected optimizations, or Custom Flag changes.

Rendering strategies covered

Private request handoff

For request-bound rendering where profile state may be private to the visitor.

  • cache scope: private-request
  • may include profile state
  • browser can preserve server-rendered content and hydrate SDK state
  • initial page event can be emitted by server/edge or browser depending on route ownership

Public permutation handoff

For customer-owned cacheable variants such as static routes, App Router Cache Components routes, Pages Router ISR routes, CDN-cached routes, or Edge runtime route handlers keyed by segment, audience, campaign, market, locale, or another non-private dimension.

  • cache scope: public-permutation
  • uses helper-created deterministic cache metadata
  • serializes application-supplied selected optimizations, changes, entries, and cache metadata
  • does not call the Experience API or derive selections from route, cookie, header, locale, or cache-key inputs
  • must not include private profile state
  • hydrates live browser state without replacing durable profile continuity when no profile is present, including after delayed persistence consent

Static handoff

For build-time or otherwise static optimized routes where selections are known without per-visitor private state.

  • cache scope: static
  • must not include private profile state
  • can preserve server-rendered content or use client-only hidden-until-ready hydration
  • hydrates live browser state without replacing durable profile continuity when no profile is present, including after delayed persistence consent

Analytics-only handoff

For routes where optimized markup is already rendered and the browser only needs analytics, profile continuity, and interaction tracking.

  • hydration mode: analytics-only
  • uses OptimizationAnalyticsRoot or the Web SDK analytics runtime
  • does not expose content resolution APIs in the browser runtime
  • does not drive App Router server content resolution

Edge request handoff

For Edge runtime route handlers that resolve optimization state, emit or skip initial page events, persist anonymous IDs, and hand state to the browser.

  • new @contentful/optimization-nextjs/edge entrypoint
  • supports Web-standard Request, Headers, and cookie snapshots
  • supports server consent resolution
  • preserves request context for page events
  • exposes a persist(response) helper for cookie persistence
  • reference private JSON route sets Cache-Control: private, no-store

Reference implementation updates

The App Router reference implementation uses bindNextjsAppRouterOptimization() and demonstrates:

  • request handoff
  • trusted request-handler-backed forwarded request context through trustedRequestHandoff: true
  • customer-owned public permutation handoff
  • analytics-only handoff
  • hidden-until-ready hydration
  • Cache Components public permutation revalidation with use cache, cacheLife(), and cacheTag()
  • a static shell with request-personalized content isolated under connection() in a private slot
  • public permutation cache middleware through the dedicated middleware-safe subpath
  • handoff-owned initial page event forwarding through nested router trackers without suppressing later SPA route tracking
  • handoff-aware server OptimizedEntry rendering through the bound App Router SDK components

Actual Edge runtime route-handler coverage lives in the dedicated nextjs-sdk_app-router_edge-runtime implementation. It uses runtime = "edge", asserts globalThis.EdgeRuntime === "edge-runtime", avoids Node-only APIs, and demonstrates request and public permutation handoff with @contentful/optimization-nextjs/edge.

The Pages Router reference implementation uses bindNextjsPagesRouterOptimization() and bindNextjsPagesRouterServerOptimization(), and includes a getStaticProps/getStaticPaths ISR public permutation route alongside the request-handoff pages. The ISR route derives selected entry rendering from the handoff-backed OptimizedEntry path instead of carrying a second selected-optimization prop.

The Next App Router and Pages Router implementation build scripts self-start mocks when /health is unavailable, wait for readiness, run next build, and stop only mock processes they started. If mocks are already running, the build treats them as externally owned and leaves cleanup to the caller or serve:stop.

The Angular Web SDK reference implementation hydrates server-provided snapshot state through hydrateOptimizationHandoff, keeping it aligned with the Web SDK handoff API instead of using lower-level snapshot hydration directly.

Documentation updates

Adds:

  • documentation/concepts/optimization-handoff-and-cache-safe-rendering.md
  • documentation/guides/rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.md

Updates:

  • Next.js App Router integration guide
  • Next.js Pages Router integration guide
  • SDK selection guide
  • analytics forwarding guide
  • React Web integration guide
  • Web SDK integration guide
  • React Native integration guide
  • related concept docs for state, profile sync, locale, consent, and stateless analytics
  • guide/concept indexes
  • internal SDK knowledge base
  • package and implementation READMEs
  • authoring blueprints and guide authoring checklist

The docs record that public/static handoff helpers serialize app-supplied selections and cache metadata. They do not discover public permutations or derive selected optimizations from route, cookie, header, locale, or cache-key inputs. Rendered Custom Flag changes must be included in app-owned cache-version rotation when those changes can affect cached output. The App Router guide documents trustedRequestHandoff: true, compact trusted request context, forwarded pageAccepted first-event ownership, the sanitizing request-handler boundary, and the Next 16 proxy.ts/proxy versus Next 13-15 middleware.ts/middleware naming split. The static/ISR guide clarifies app-owned cache inputs, Next.js invalidation tags, and SDK-generated handoff.cache.key metadata. The Pages Router guide documents request-handoff consent defaults, and the static/ISR guide includes cache-control validation with s-maxage=60.

Bundle-size policy

This PR adds bundle budgets for new public entrypoints, removes or replaces budgets for removed entrypoints, and resets affected package budgets after full package builds and size reports.

Budget reset policy used for this PR:

  • measured every package entry with pnpm size:report
  • set each budget to at least 100 bytes of gzip headroom
  • rounded each budget up to the nearest 100 bytes
  • lowered loose budgets and raised tight budgets from the measured outputs
  • confirmed the final package graph with pnpm size:check

Notable budget coverage includes:

  • Web SDK handoff, analytics, presentation, tracking-attributes, web-components, and UMD bundles
  • React Web SDK handoff, analytics, tracking-attributes, index, and router adapter entrypoints
  • React Native preview and preview-support entrypoints
  • Next.js cache-middleware, request-handler, server, edge, App Router, Pages Router, and tracking-attributes entrypoints
  • Core bridge-support, runtime, entry-source, preview-support, and index entrypoints
  • Node, API client, API schemas, and preview panel entrypoints

Bundle budget overruns were treated as validation evidence during implementation. The final budget pass built all packages, generated size reports for all packages, reset budgets from measured outputs, and passed pnpm size:check.

E2E and test coverage

Adds or updates coverage for:

  • deterministic selection fingerprints and cache keys
  • public permutation cache metadata creation
  • cache-safety warnings and assertions
  • framework-neutral handoff creation
  • Node request handoff creation and private-request cache enforcement
  • Web handoff hydration and public Web handoff state hydration helper behavior
  • static/public profileless handoff hydration preserving durable continuity, including after delayed persistence consent
  • stale async Web handoff and analytics hydration suppression
  • Web current-route initialPageEvent: "skip" deduplication for a supplied route key
  • React OptimizationRoot handoff and page-event wiring behavior, including skip-only route marking without a payload builder
  • React OptimizationProvider provider-owned hydration and rehydration of changed handoff state after mount
  • React OptimizationProvider cache-safety assertion before initial snapshot runtime creation
  • React OptimizationAnalyticsRoot
  • Next.js public permutation cache middleware validation and response preservation
  • Next.js request-handler preservation of existing rewrites while applying SDK context and cookie persistence
  • domain/path-scoped anonymous-ID cookie expiration when persistence is denied
  • Pages Router request handoff defaults from resolved server consent
  • Pages Router ISR rendering from handoff-backed selected entry state
  • request-handler forwarding of compact { consent, pageAccepted, profileId } context, effective forwarded cookies, and App Router trusted context consumption without duplicate profile fetches
  • forwarded pageAccepted controlling trusted App Router initial browser page-event skip/emit behavior
  • forged raw App Router forwarded request-context headers being ignored without trusted opt-in
  • App Router server OptimizedEntry resolution from request and public permutation handoff state
  • analytics-only handoff isolation from browser content resolution
  • mock Experience profile lookup returning not-found responses for unknown profile IDs
  • Next.js App Router, Pages Router, and Edge runtime handoff flows
  • baseline empty-selection public permutations across App Router, Edge, and Pages Router E2E
  • Pages Router ISR cache-control assertion with s-maxage=60
  • private Edge request route Cache-Control: private, no-store
  • public @contentful/optimization-nextjs/edge package export guard
  • real Next middleware override header behavior in request-handler tests
  • offline queue recovery stability across rapid network state changes

Primary E2E files:

  • lib/e2e-web/e2e/handoff.spec.ts
  • lib/e2e-web/e2e/offline-queue-recovery.spec.ts

Validation

Validation evidence for this branch includes:

  • pnpm build
  • pnpm size:report
  • pnpm size:check
  • pnpm lint
  • pnpm typecheck
  • pnpm test:unit
  • pnpm guides:check
  • pnpm knowledge:check
  • pnpm format:check
  • pnpm build:pkgs
  • Web SDK typecheck, handoff unit tests, build, and size check
  • React Web SDK typecheck, provider handoff unit tests, build, and size check
  • Next.js SDK typecheck, targeted request/App Router/package unit tests, and build
  • Next.js App Router server focused unit test and targeted ESLint
  • Core SDK targeted handoff, preview, and state-signal unit tests
  • Mocks typecheck, focused experience-handler unit test, and targeted ESLint
  • React Native typecheck, unit tests, build, dev:test, and size check
  • targeted Next.js App Router, Pages Router, and Edge runtime handoff E2E
  • Edge implementation lint, typecheck, build, and scoped E2E
  • targeted Prettier checks for touched package, implementation, E2E, SDK knowledge, guide, concept, and package JSON files
  • git diff --check

Additional focused validation included:

  • Core preview-support bundle remediation measured with @contentful/optimization-core size:report
  • final full package build and full size report before budget reset
  • final pnpm size:check after budget reset
  • App Router handoff E2E Chromium: 7 passed, 5 skipped
  • Edge handoff E2E Chromium: 3 passed, 9 skipped
  • Pages Router handoff E2E Chromium coverage for public permutation routes
  • trusted App Router forwarded pageAccepted unit coverage
  • mock Experience profile lookup focused unit coverage
  • targeted docs validation with pnpm guides:check and pnpm knowledge:check

Reviewer checklist

Please pay particular attention to:

  • whether the cache scopes and assertion behavior make the private/public/static boundary clear
  • whether static/public profileless handoffs hydrate the current page without poisoning durable browser continuity, including after delayed persistence consent
  • whether content-capable handoffs clear stale browser content state when new route handoff content state is absent
  • whether helper-created public permutation metadata is the right API shape for static, Cache Components, Pages Router ISR, middleware, and Edge runtime routes
  • whether the Web handoff helper is the right customer-adapter API boundary for browser SDK state hydration
  • whether initialPageEvent: "emit" | "skip" is assigned consistently across server, edge, browser ownership, and nested router trackers
  • whether handoff-owned initialPageEvent: "skip" semantics prevent duplicate browser page events for server-accepted initial routes without breaking browser-owned SPA route tracking
  • whether trusted forwarded App Router handoff correctly uses sanitized pageAccepted for first-event ownership without trusting raw client-supplied headers
  • whether async state interceptors cannot publish stale handoff state after a newer handoff starts
  • whether Pages Router request handoff consent defaults are carried into client hydration correctly
  • whether trusted forwarded request-context consumption is explicit enough and only used behind the sanitizing request-handler path
  • whether existing rewrites remain chainable while request-context SDK work still applies
  • whether analytics-only handoff behavior avoids exposing browser content APIs while still preserving tracking needs
  • whether analytics-only handoff state is kept out of App Router server content resolution
  • whether the /edge API shape is the right replacement for the removed /esr subpath
  • whether persistent React roots rehydrate changed handoff state correctly across Pages Router SPA navigations
  • whether provider-owned hydration and root-owned page-event wiring are clear and consistent across React Web and Next.js bound components
  • whether the App Router Cache Components, static private slot, Pages Router ISR, and Edge runtime examples are realistic enough for customers to copy or adapt
  • whether public/static handoff docs avoid implying that the SDK discovers public permutations or derives selected optimizations from request inputs

Risk areas

  • The PR changes public API surfaces across Core, Web, React Web, Next.js, Node, and React Native packages.
  • The Next.js /esr subpath removal changes the published Next.js package export surface.
  • The Next.js create-style setup API removals and React Native beta initialize rename need migration notes for affected adopters.
  • Cache-safe rendering relies on customers choosing the correct cache scope and avoiding private profile state in public/static caches.
  • Public permutation cacheability relies on application-owned finite permutation registries, cache versions, and invalidation tags where applicable.
  • Rendered Custom Flag changes require app-owned cache-version rotation when those changes can affect cached output.
  • Initial page event ownership is subtle because request, edge, and browser runtimes can each be responsible depending on the route.
  • Trusted forwarded request-context consumption depends on using trustedRequestHandoff: true only behind the sanitizing request handler.
  • App Router Server Component to Client Component boundaries require serializable props, which is why the bound Next.js root evaluates buildPagePayload on the server and passes initialPagePayload to React Web.
  • React Web and Next.js provider/root prop boundaries need to stay aligned so provider-only integrations do not silently ignore root-only page-event props.
  • App Router request-personalized routes must create request handoff state in the route server subtree that renders server entries, not only in a shared layout that client navigation can preserve.
  • Custom adapters must validate the handoff/cache-safety boundary before using the state-only Web handoff hydration helper.
  • The reference implementations exercise several rendering modes, so reviewers should check both API ergonomics and route-level behavior.

@phobetron
Charles Hudson (phobetron) force-pushed the NT-3708_implement-ssg-isr-esr-handoff-support branch 4 times, most recently from 51ae699 to a5626b4 Compare July 22, 2026 09:34
@phobetron
Charles Hudson (phobetron) marked this pull request as ready for review July 22, 2026 09:35
@phobetron
Charles Hudson (phobetron) force-pushed the NT-3708_implement-ssg-isr-esr-handoff-support branch 8 times, most recently from d1884f7 to 3b3463a Compare July 23, 2026 09:12
@phobetron Charles Hudson (phobetron) changed the title feat(nextjs): add cache-safe optimization handoff support feat(nextjs)!: add cache-safe optimization handoff support Jul 23, 2026
@phobetron Charles Hudson (phobetron) changed the title feat(nextjs)!: add cache-safe optimization handoff support feat(nextjs): add cache-safe optimization handoff support Jul 23, 2026
@phobetron
Charles Hudson (phobetron) force-pushed the NT-3708_implement-ssg-isr-esr-handoff-support branch 2 times, most recently from aaa4389 to 322e290 Compare July 25, 2026 10:04
@wiz-inc-38d59fb8d7

wiz-inc-38d59fb8d7 Bot commented Jul 25, 2026

Copy link
Copy Markdown

Wiz Scan Summary

Scanner Findings
Vulnerability Finding Vulnerabilities -
Data Finding Sensitive Data -
Secret Finding Secrets -
IaC Misconfiguration IaC Misconfigurations -
SAST Finding SAST Findings 2 Medium
Software Management Finding Software Management Findings -
Total 2 Medium

View scan details in Wiz

To detect these findings earlier in the dev lifecycle, try the Wiz Code extension for VS Code, JetBrains, or Visual Studio.

@phobetron
Charles Hudson (phobetron) force-pushed the NT-3708_implement-ssg-isr-esr-handoff-support branch 4 times, most recently from a4d3aa0 to 33de5d5 Compare July 27, 2026 11:06
Comment thread lib/e2e-web/e2e/handoff.spec.ts Fixed
Comment thread lib/e2e-web/e2e/handoff.spec.ts Fixed
@phobetron
Charles Hudson (phobetron) force-pushed the NT-3708_implement-ssg-isr-esr-handoff-support branch 2 times, most recently from 7a45e70 to a83c1ef Compare July 27, 2026 13:01
@phobetron
Charles Hudson (phobetron) force-pushed the NT-3708_implement-ssg-isr-esr-handoff-support branch 12 times, most recently from 4d38dd6 to f9e058e Compare July 28, 2026 13:24
Add explicit, cache-safe Optimization SDK handoff primitives for static,
request-time, ISR, App Router Cache Components, Edge, and analytics-only
rendering flows.

Core SDK:
- Add framework-neutral handoff state, cache metadata, cache-key helpers,
  deterministic selection fingerprints, cache-safety checks, and selected-entry
  resolution helpers.
- Widen state interceptors for profileless selection state and sparse
  own-property patch semantics.
- Restore preview selected optimizations and Custom Flag changes independently
  when preview overrides reset.

Web SDK:
- Add browser content and analytics handoff types and hydration helpers.
- Add analytics-only runtime hydration.
- Guard async handoff hydration against stale state publication.
- Hydrate static/public profileless handoffs into live state without overwriting
  durable LocalStore continuity, including after delayed persistence consent.
- Keep private/profile-backed handoffs on the normal durable persistence path.
- Let skip-only current-page tracking mark a route accepted without requiring a
  page payload builder.

React Web SDK:
- Add handoff, analytics, and tracking-attributes entrypoints.
- Teach providers and roots to hydrate handoff state, preserve server-rendered
  content, support analytics-only roots, and keep route tracking ownership
  explicit.
- Preserve initial-route skip semantics through StrictMode replay while still
  emitting later route changes.

Next.js SDK:
- Add canonical App Router, Pages Router, Edge, cache-middleware, and request
  handoff helpers.
- Add public permutation handoff/cache metadata helpers for app-owned cacheable
  selections.
- Make App Router forwarded request-context consumption explicit with
  trustedRequestHandoff: true.
- Forward compact trusted request context containing consent, pageAccepted, and
  profileId instead of full optimization data.
- Preserve existing rewrite responses while applying SDK request context and
  eligible anonymous-ID cookie persistence.
- Expire domain/path-scoped anonymous-ID cookies with matching options when
  persistence is denied.
- Preserve terminal redirects and non-middleware app responses without SDK
  mutation.
- Replace the previous /esr export with /edge.

Node SDK:
- Add createRequestHandoffFromData for framework-neutral private request handoff
  creation from completed server optimization data.

React Native SDK:
- Rename ContentfulOptimization.create() to ContentfulOptimization.initialize()
  and remove the beta create() API.
- Preserve omitted persisted-continuity fields while allowing own-present
  undefined fields to clear stored continuity.

Reference implementations and E2E:
- Update Next.js App Router and Pages Router implementations for request,
  public permutation, analytics-only, static, ISR, and Edge handoff flows.
- Remove duplicate selected-optimization state from the Pages Router ISR
  reference route and render from the handoff-backed path.
- Add explicit Cache-Control: private, no-store to the private Edge request
  handoff route.
- Add coverage for baseline empty-selection public permutations, analytics-only
  handoff, Pages ISR cache-control, middleware overrides, and offline queue
  recovery.

Docs and knowledge base:
- Update public guides, concepts, and SDK knowledge for cache-safe handoff
  behavior, continuity rules, trusted request context, cache invalidation, and
  analytics hydration ownership.
- Document that rendered Custom Flag changes must be included in app-owned
  cache-version rotation for public/static handoff routes.
- Document Next 16 proxy.ts/proxy naming and Next 13-15 middleware.ts/middleware
  naming for App Router request-context setup.
- Clarify that existing rewrites are preserved while request-context SDK work is
  still applied.

Bundle policy:
- Refresh bundle budgets from full package builds and size reports.
- Reset budgets across package manifests so each budget has at least 100B of
  gzip headroom, rounded up to the nearest 100B.
- Lower loose budgets and raise tight budgets based on the current measured
  outputs.
- Add missing budgets for real React Native preview and React Web router adapter
  entrypoints.
- Confirm the final package graph passes size checks.

Validation:
- pnpm build
- pnpm size:report
- pnpm size:check
- pnpm lint
- pnpm typecheck
- pnpm test:unit
- pnpm knowledge:check
- pnpm guides:check
- pnpm format:check
- pnpm build:pkgs
- Targeted Web SDK, React Web SDK, Next.js SDK, Core SDK, React Native, mocks,
  and implementation checks
- Targeted Next.js App Router, Pages Router, and Edge runtime handoff E2E
- git diff --check

Versioning note:
- Merge this PR with this non-major squash commit message so shared package
  releases stay on feature-level version bumps.
- Follow up with a Next.js-only release-signaling PR for the /esr replacement
  and create-style setup API removals, so the CD system can assign the major
  version bump only to @contentful/optimization-nextjs.

[[NT-3708](https://contentful.atlassian.net/browse/NT-3708)]
@phobetron
Charles Hudson (phobetron) force-pushed the NT-3708_implement-ssg-isr-esr-handoff-support branch from f9e058e to b000b65 Compare July 28, 2026 15:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants