feat(nextjs): add cache-safe optimization handoff support#379
Open
Charles Hudson (phobetron) wants to merge 1 commit into
Open
feat(nextjs): add cache-safe optimization handoff support#379Charles Hudson (phobetron) wants to merge 1 commit into
Charles Hudson (phobetron) wants to merge 1 commit into
Conversation
Charles Hudson (phobetron)
force-pushed
the
NT-3708_implement-ssg-isr-esr-handoff-support
branch
4 times, most recently
from
July 22, 2026 09:34
51ae699 to
a5626b4
Compare
Charles Hudson (phobetron)
marked this pull request as ready for review
July 22, 2026 09:35
Charles Hudson (phobetron)
force-pushed
the
NT-3708_implement-ssg-isr-esr-handoff-support
branch
8 times, most recently
from
July 23, 2026 09:12
d1884f7 to
3b3463a
Compare
Add explicit optimization handoff primitives for rendering personalized Contentful Optimization experiences through static, ISR, request-time, and edge-owned routes without relying on implicit browser-only state. Core SDK: - Add framework-neutral handoff types for selected optimization state, managed entry snapshots, and cache metadata. - Add deterministic selection fingerprints and cache-key helpers. - Add helpers for resolving baseline entries from customer-owned selections. - Add cache-safety warnings for public/static handoffs. Web SDK: - Add browser-facing content and analytics handoff types. - Add handoff hydration for preserving server-rendered content or hiding client-only content until browser state is ready. - Add initializeOptimizationAnalyticsRuntime for analytics-only browser runtimes that still need analytics and interaction tracking. - Treat initialPageEvent "skip" as an accepted current route so browser trackers do not duplicate page events already accepted by server handoffs. React Web SDK: - Add handoff, analytics, and tracking-attributes subpath exports. - Teach OptimizationProvider to own handoff state, managed-entry prefetch handoff entries, and content hydration presentation. - Teach OptimizationRoot to compose provider state, live updates, route keys, and initial page event payload builders. - Hydrate changed handoff state into an existing live provider runtime after mount so persistent roots can consume new server handoffs. - Add OptimizationAnalyticsRoot for analytics-only hydration. - Keep tracking attributes available independently from the full React runtime. Next.js SDK: - Add canonical handoff helpers for App Router and Pages Router integrations. - Rename bound component helper APIs from create* names to bind* names. - Rename the server runtime setup API from create* to configure* naming. - Split bound provider and root props so providers own handoff, hydration, and managed-entry prefetch while roots add page-event wiring. - Add the edge entrypoint with configureNextjsEdgeOptimization and createEdgeRequestHandoff. - Add a dedicated /cache-middleware subpath with createNextjsCacheMiddleware. - Document binding/configuration helpers as shared-runtime setup surfaces, not isolation boundaries. - Clear stale App Router server content handoff state for analytics-only handoffs. - Replace the previous /esr export with /edge. React Native SDK: - Rename ContentfulOptimization.create() to ContentfulOptimization.initialize() to match the single-runtime architecture. - Remove the beta create() API instead of carrying a legacy alias. Node SDK: - Add createRequestHandoffFromData for framework-neutral request handoff creation from completed Node request optimization data. - Reject public/static cache scopes when request handoff data includes profile state. Reference implementations and tests: - Update Next.js App Router and Pages Router implementations to consume the new bind* and configure* APIs. - Expand the Next.js App Router implementation with request handoff, customer-owned selection handoff, analytics-only, hidden-until-ready, edge request, edge selection, edge HTML, and cache middleware routes. - Compute public cache metadata from shared fixture data instead of hard-coded cache keys. - Add E2E coverage for handoff routes, ISR cache headers, raw response metadata, and Edge HTML analytics hydration. - Add React Web route tracker coverage for SSR-owned initial route skips and browser-owned SPA navigation emissions. - Add React Web and Next.js coverage for provider-owned hydration and the bound provider/root prop split. Documentation: - Add a dedicated guide for rendering personalized Next.js routes with static, ISR, and edge handoffs. - Update public guides, package READMEs, implementation READMEs, internal SDK knowledge, and authoring blueprints for initialize, bind, and configure API terminology. - Document provider/root ownership for React Web and Next.js handoff hydration. - Correct selected-optimization guidance to match attached optimization entry fields.nt_experience_id values. - Add package maintenance guidance that shared-runtime SDK setup APIs must not use create* or factory wording unless they create truly isolated state. - Add a package-wide AGENTS exception for standardized build-time replacement globals such as __OPTIMIZATION_VERSION__. Bundle policy: - Add budgets for new public entrypoints, including Next.js cache-middleware. - Remove budgets for removed entrypoints. - Reset affected budgets to match the current handoff-capable bundle outputs, including the React Web route tracking fix. Validation: - Full Next.js App Router E2E passes. - Full Next.js Pages Router E2E passes. - React Web focused route tracker unit tests pass. - React Web typecheck, unit tests, build, and size check pass. - Next.js typecheck, unit tests, build, and size check pass. - React Native typecheck, unit tests, build, dev:test, and size check pass. - Web analytics and React Web targeted tests pass. - pnpm build:pkgs passes and refreshed Next.js implementation installs. - Next.js App Router and Pages Router implementation typecheck and lint pass. - pnpm lint, pnpm format:check, pnpm guides:check, pnpm knowledge:check, and git diff --check pass. 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)]
Charles Hudson (phobetron)
force-pushed
the
NT-3708_implement-ssg-isr-esr-handoff-support
branch
from
July 23, 2026 09:28
3b3463a to
aaa4389
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements explicit, cache-safe Optimization SDK handoff support for Next.js static, ISR, request-time, and edge 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, an edge-safe Next.js runtime, 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 the API names match the single-runtime architecture.
Request-derived profile handoffs now fail fast when paired with public or static cache scopes, keeping private request state out of shared cache paths.
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-only release-signaling PR is needed for the
/esrreplacement and create-style setup API removals, so the CD system can assign the major version bump only to@contentful/optimization-nextjs.Why
Optimized content rendering needs to support more than browser-owned runtime state. This PR makes the state handoff explicit for:
The implementation separates:
Review map
Suggested review order:
Core model:
packages/universal/core-sdk/src/handoff.tspackages/universal/core-sdk/src/handoff.test.tsBrowser hydration/runtime behavior:
packages/web/web-sdk/src/handoff.tspackages/web/web-sdk/src/analytics.tspackages/web/web-sdk/src/handoff-state.tspackages/web/web-sdk/src/ContentfulOptimization.tspackages/web/web-sdk/src/*handoff*.test.tspackages/web/web-sdk/src/analytics.test.tspackages/web/web-sdk/src/ContentfulOptimization.test.tsReact Web integration:
packages/web/frameworks/react-web-sdk/src/root/OptimizationRoot.tsxpackages/web/frameworks/react-web-sdk/src/root/OptimizationAnalyticsRoot.tsxpackages/web/frameworks/react-web-sdk/src/provider/OptimizationProvider.tsxpackages/web/frameworks/react-web-sdk/src/auto-page/useAutoPageEmitter.tspackages/web/frameworks/react-web-sdk/src/**Next.js public APIs and edge behavior:
packages/web/frameworks/nextjs-sdk/src/handoff.tspackages/web/frameworks/nextjs-sdk/src/edge.tspackages/web/frameworks/nextjs-sdk/src/cache-middleware.tspackages/web/frameworks/nextjs-sdk/src/forwarded-request-headers.tspackages/web/frameworks/nextjs-sdk/src/app-router-server.tsxReference implementations and E2E evidence:
implementations/nextjs-sdk_app-router/**implementations/nextjs-sdk_pages-router/**implementations/web-sdk_angular/src/app/services/optimization.tslib/e2e-web/e2e/handoff.spec.tsDocumentation:
documentation/concepts/optimization-handoff-and-cache-safe-rendering.mddocumentation/guides/rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.mddocumentation/internal/sdk-knowledge/**Public API changes
Core SDK
Adds
@contentful/optimization-corehandoff support:OptimizationSelectionStateOptimizationCacheScopeOptimizationCacheMetadataOptimizationHandoffOptimizationCacheSafetyWarningcreateSelectionFingerprintcreateOptimizationCacheKeyresolveEntriesForSelectionscreateHandoffFromSelectionsgetOptimizationCacheSafetyWarningsThese APIs let SDKs and customer integrations describe the optimization state used during server/static/edge rendering and attach cache-safety metadata to the rendered output.
Web SDK
Adds browser handoff and analytics-only runtime support:
ContentOptimizationHydrationModeOptimizationHydrationModeContentOptimizationHandoffAnalyticsOptimizationHandoffBrowserOptimizationHandoffhydrateOptimizationHandoffinitializeOptimizationAnalyticsRuntimehydrateOptimizationAnalyticsHandoffNew subpath exports:
@contentful/optimization-web/handoff@contentful/optimization-web/analyticsContentfulOptimization.trackCurrentPage({ initialPageEvent: "skip" })marks the supplied route key as accepted without emitting a browser page event. 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.React Web SDK
Adds handoff-aware provider and root support:
OptimizationProvideraccepts handoff state, hydration behavior, and managed-entry prefetch handoff entries.OptimizationProviderhydrates changed handoff state into an existing live runtime so persistent roots can consume new server handoffs after mount.OptimizationRootcomposes provider state, live updates, route identity, and initial page event payload builders.OptimizationAnalyticsRootsupports analytics-only hydration.initialPagePayloadfor server-framework bridges that cannot pass functions into client components.initialPageEvent: "skip"only for the first eligible mounted route, so later SPA route changes emit as browser-owned page events.New subpath exports:
@contentful/optimization-react-web/handoff@contentful/optimization-react-web/analytics@contentful/optimization-react-web/tracking-attributesReact Native SDK
Renames manual runtime setup from
ContentfulOptimization.create()toContentfulOptimization.initialize(). The betacreate()API is removed rather than preserved as a legacy alias because the React Native SDK owns one shared runtime instance per app.Next.js SDK
Adds canonical Next.js handoff and setup helpers:
createHandoffFromSelectionsaddBrowserHandoffMetadatabindNextjsAppRouterOptimizationbindNextjsPagesRouterOptimizationbindNextjsPagesRouterServerOptimizationconfigureNextjsServerOptimizationconfigureNextjsEdgeOptimizationcreateEdgeRequestHandoffcreateNextjsCacheMiddlewarecreateNextjsCacheMiddlewareis exported from@contentful/optimization-nextjs/cache-middlewareso proxy or middleware code can use it without resolving the App Routerreact-serverentry.Bound App Router and Pages Router components split provider and root props: providers own handoff, hydration, and managed-entry prefetch, while roots add route identity and initial page-event payload wiring.
Adds App Router server support for request-local handoff state so server
OptimizedEntrycan resolve entries from the active request or selection handoff. Analytics-only handoffs intentionally do not drive server content resolution.App Router and Edge request handoff helpers surface the request-handoff cache-safety rejection so request-derived profile state cannot be handed to public/static cache scopes.
The App Router bound root keeps
buildPagePayloadas the developer-facing API and converts it to serializableinitialPagePayloadbefore crossing the Server Component to Client Component boundary.Adds the new package subpaths:
@contentful/optimization-nextjs/cache-middleware@contentful/optimization-nextjs/edgeRemoves the previous package subpath:
@contentful/optimization-nextjs/esrNode SDK
Adds:
createRequestHandoffFromDataThis lets Node/server integrations create framework-neutral handoff payloads from completed request optimization data.
createRequestHandoffFromDataaccepts serialized request optimization data, optional managed entry snapshots, and cache metadata. It throws aTypeErrorwhen completed request data includes profile state and the handoff cache scope ispublic-permutationorstatic.Rendering strategies covered
Private request handoff
For request-bound rendering where profile state may be private to the visitor.
private-requestPublic permutation handoff
For customer-owned cacheable variants such as ISR routes keyed by segment, audience, campaign, or other non-private dimensions.
public-permutationStatic handoff
For build-time or otherwise static optimized routes where selections are known without per-visitor private state.
staticAnalytics-only handoff
For routes where optimized markup is already rendered and the browser only needs analytics, profile continuity, and interaction tracking.
analytics-onlyOptimizationAnalyticsRootor the Web SDK analytics runtimeEdge request handoff
For edge routes that resolve optimization state, emit or skip initial page events, persist anonymous IDs, and hand state to the browser.
@contentful/optimization-nextjs/edgeentrypointpersist(response)helper for cookie persistenceMigration notes
/esrwas replaced by/edge@contentful/optimization-nextjs/esrhas been removed.Use:
The new edge API is the supported path for edge-owned request handoff behavior.
Shared-runtime setup helpers were renamed by purpose
Next.js factory-style setup exports were removed in favor of purpose-named helpers:
createNextjsAppRouterOptimization()->bindNextjsAppRouterOptimization()createNextjsPagesRouterOptimization()->bindNextjsPagesRouterOptimization()for the browser component setcreateNextjsPagesRouterOptimization()from/pages-router/server->bindNextjsPagesRouterServerOptimization()createNextjsOptimization()->configureNextjsServerOptimization()The helpers configure or bind shared SDK runtime surfaces; they are not request or visitor isolation contexts.
React Native manual runtime setup was renamed
Use
ContentfulOptimization.initialize()for explicit React Native SDK runtime setup. The betacreate()API is removed.Handoff is explicit
Next.js integrations should pass explicit handoff payloads into the React/Web hydration layer instead of relying on implicit browser-only state for server/static/edge-rendered personalized content.
Cache ownership is explicit
Public or static rendered output must not carry private profile state. Public permutation routes should provide a customer-owned cache key. The Core SDK exposes warning helpers to make these cases visible, and request handoff helpers reject request-derived profile state when paired with public/static cache scopes.
App Router server content resolution is handoff-aware
App Router server
OptimizedEntryresolves from the active request or selection handoff. This keeps the consumer API centered on the bound SDK components instead of requiring route code to manually thread selected optimizations through every server-rendered entry.For request-personalized App Router pages, the reference implementation creates the current request handoff in a route-local server shell before returning the subtree that contains server
OptimizedEntry, while keeping the persistent browser root in the shared layout.Reference implementation updates
The App Router reference implementation uses the new
bindNextjsAppRouterOptimization()andconfigureNextjsEdgeOptimization()setup helpers and demonstrates:OptimizedEntryrendering through the bound App Router SDK componentsThe App Router implementation separates request-owned routes from static/ISR public handoff routes with route groups, keeping the root layout request-neutral so public handoff routes prerender with revalidation. Request-owned routes keep a persistent shared browser root while route-local server shells create the current request handoff before returning server entry subtrees.
The Pages Router reference implementation uses
bindNextjsPagesRouterOptimization()andbindNextjsPagesRouterServerOptimization(), and aligns with the handoff contract and shared utility behavior, including handoff-owned initial page event forwarding through the nested router tracker.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.E2E and test coverage
Adds or updates coverage for:
initialPageEvent: "skip"deduplication for a supplied route keyOptimizationRoothandoff and page-event wiring behaviorOptimizationProviderprovider-owned hydration and rehydration of changed handoff state after mountOptimizationAnalyticsRootOptimizedEntryresolution from request and selection handoff state/page-twohard loads and/to/page-twoclient navigationPrimary new E2E file:
lib/e2e-web/e2e/handoff.spec.tsDocumentation updates
Adds:
documentation/concepts/optimization-handoff-and-cache-safe-rendering.mddocumentation/guides/rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.mdThe rendering guide documents Edge handoff with route-handler/
Responseownership and keeps App Router React page rendering in the Edge runtime out of scope until that surface is validated.Updates:
fields.nt_experience_idvaluesparamsexamplesBundle-size policy
This PR adds bundle budgets for new public entrypoints and removes budgets for removed entrypoints.
Notable changes include:
handoffandanalyticsentrypoint budgetshandoff,analytics, andtracking-attributesentrypoint budgetscache-middlewareentrypoint budgetsesr.*toedge.*Bundle budget overruns were treated as validation evidence during implementation. The budget changes reflect the current public entrypoint shape; future budget tightening should be handled as separate optimization work.
Validation
Validation evidence for this branch includes:
pnpm size:checkpnpm build:pkgspnpm implementation:run -- nextjs-sdk_app-router implementation:installpnpm implementation:run -- nextjs-sdk_pages-router implementation:installpnpm implementation:run -- nextjs-sdk_app-router buildpnpm --filter @contentful/optimization-nextjs typecheckpnpm --filter @contentful/optimization-nextjs test:unitpnpm --filter @contentful/optimization-nextjs buildpnpm --filter @contentful/optimization-nextjs size:checkpnpm --filter @contentful/optimization-react-web typecheckpnpm --filter @contentful/optimization-react-web test:unitpnpm --filter @contentful/optimization-react-web buildpnpm --filter @contentful/optimization-react-web size:checkContentfulOptimizationunit tests for current-route skip behaviorcreateRequestHandoffFromDatapnpm guides:checkpnpm knowledge:checkpnpm lintpnpm format:checkgit diff --checkAdditional branch validation performed during the implementation included package, implementation, documentation, and knowledge-base checks documented in the branch work.
Reviewer checklist
Please pay particular attention to:
initialPageEvent: "emit" | "skip"is assigned consistently across server, edge, browser ownership, and nested router trackersinitialPageEvent: "skip"semantics prevent duplicate browser page events for server-accepted initial routes without breaking browser-owned SPA route tracking/edgeAPI shape is the right replacement for the removed/esrsubpathOptimizedEntryrenderinginitialPagePayloadbridge is clearly an adapter/runtime serialization mechanism whilebuildPagePayloadremains the normal consumer-facing APIRisk areas
/esrsubpath removal changes the published Next.js package export surface.buildPagePayloadon the server and passesinitialPagePayloadto React Web.