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
4 times, most recently
from
July 22, 2026 18:37
a244b15 to
fa9e129
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 an analytics-only runtime for server/static/edge-rendered markup that still needs browser 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 OptimizationRoot to accept handoff state, hydration mode, 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. - Consume initialPageEvent "skip" only for the first eligible mounted route so later SPA navigations emit normally. Next.js SDK: - Add canonical handoff helpers for App Router and Pages Router integrations. - Add the edge entrypoint with createNextjsEdgeOptimization and createEdgeRequestHandoff. - Add a dedicated /cache-middleware subpath with createNextjsCacheMiddleware. - Keep App Router factories as bound singleton-style component sets, not isolation boundaries. - Clear stale App Router server content handoff state for analytics-only handoffs. - Replace the previous /esr export with /edge. 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: - 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. Documentation: - Add a dedicated guide for rendering personalized Next.js routes with static, ISR, and edge handoffs. - Fix App Router params examples for Promise-based params. - Align guide and README cache metadata examples with computed tags. - Clarify request-context ownership, cache scope boundaries, and supported rendering-mode responsibilities. 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, build, and size check pass. BREAKING CHANGE: @contentful/optimization-nextjs/esr is removed. Use @contentful/optimization-nextjs/edge and the new edge handoff APIs instead. [[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 22, 2026 21:06
fa9e129 to
7472d08
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 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.
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
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:
ContentOptimizationHydrationModeOptimizationHydrationModeContentOptimizationHandoffAnalyticsOptimizationHandoffBrowserOptimizationHandoffhydrateOptimizationHandoffcreateOptimizationAnalyticsRuntimehydrateOptimizationAnalyticsHandoffNew 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 root support:
OptimizationRootaccepts handoff state, hydration behavior, route identity, and initial page event payload builders.OptimizationProviderhydrates changed handoff state into an existing live runtime so persistent roots can consume new server handoffs after mount.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-attributesNext.js SDK
Adds canonical Next.js handoff helpers:
createHandoffFromSelectionsaddBrowserHandoffMetadatacreateNextjsEdgeOptimizationcreateEdgeRequestHandoffcreateNextjsCacheMiddlewarecreateNextjsCacheMiddlewareis exported from@contentful/optimization-nextjs/cache-middlewareso proxy or middleware code can use it without resolving the App Routerreact-serverentry.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 persistenceBreaking and migration 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.
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 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 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 behaviorOptimizationProviderrehydration 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:
paramsexamplesBundle-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 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 buildpnpm --filter @contentful/optimization-react-web size:checkContentfulOptimizationunit tests for current-route skip behaviorcreateRequestHandoffFromDatapnpm guides:checkpnpm knowledge: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 is a breaking package export change.buildPagePayloadon the server and passesinitialPagePayloadto React Web.