Skip to content

🐞 fix(nextjs): Fix Next.js server page context/state handoff for query-parameters#339

Merged
Charles Hudson (phobetron) merged 1 commit into
mainfrom
NT-3568_fix-missing-query-bug
Jun 29, 2026
Merged

🐞 fix(nextjs): Fix Next.js server page context/state handoff for query-parameters#339
Charles Hudson (phobetron) merged 1 commit into
mainfrom
NT-3568_fix-missing-query-bug

Conversation

@phobetron

@phobetron Charles Hudson (phobetron) commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

This fixes server-side audience matching for rules that depend on URL query parameters. The supported Next.js server optimization path can now populate context.page from the current request URL, including query parameters, so a request such as /?test=true can evaluate query-based audience rules instead of silently rendering baseline content.

While investigating the bug, the issue turned out to span more than URL parsing. Next.js App Router splits request handling, Server Component rendering, and browser startup across different boundaries. The previous request-handler API mixed request context capture, page() execution, consent resolution, and cookie persistence in middleware/proxy code, which made it hard for Server Components to reliably receive page URL context without duplicating work. This PR separates those responsibilities and documents the supported integration paths.

What changed

  • Added shared page-context creation in Core so request URLs are normalized into SDK-compatible page.path, page.search, page.query, page.referrer, and page.url.
  • Updated the Next.js server helpers to derive page context from:
    • direct request objects,
    • sanitized proxy/middleware-forwarded request URL headers,
    • explicit App Router route state via createNextjsPageContext().
  • Replaced the page-producing Next.js request handler with createNextjsOptimizationContextHandler(), which only forwards sanitized request context for Server Components.
  • Added @contentful/optimization-nextjs/esr for edge/request-rendered flows that own both the incoming Request and outgoing Response, including anonymous ID persistence.
  • Added explicit server-to-browser Optimization state handoff through:
    • serverOptimizationState on React Web provider/root APIs,
    • NextjsOptimizationState for shared App Router layouts where page data is produced below the provider.
  • Introduced internal first-party bridge-support entry points for controlled state hydration and preview-panel access, replacing public symbol/register-preview wiring.
  • Added diagnostics for conflicting explicit page context versus page event properties.
  • Updated Next.js SSR and hybrid reference implementations to use the new request-context and state-handoff model.
  • Updated package READMEs, Next.js integration guides, and related concepts for consent, profile synchronization, locale handling, and server/stateless interaction tracking.

Why the scope expanded

A narrow fix that only parsed query strings would not have made the supported Next.js App Router flow reliable. Server Components do not naturally receive the full request URL, and the old middleware helper tried to solve that by performing SDK work at the request layer. That created unclear ownership over consent, page events, cookie persistence, and browser handoff.

This PR makes the boundaries explicit:

  • proxy/middleware captures request URL context only;
  • Server Components own getNextjsServerOptimizationData() and the initial server page() call;
  • ESR helpers own request/response-based rendering flows;
  • React/Next client code receives server-returned Optimization data through an explicit handoff API;
  • preview and state hydration use internal bridge support instead of consumer-visible Core internals.

Test coverage added or updated

  • Core stateless page-event tests cover forwarding request page query context.
  • EventBuilder tests cover context.page construction and explicit page-context precedence.
  • Core bridge tests cover first-party bridge access and Optimization data hydration.
  • Next.js server tests cover forwarded request URL headers, explicit page options, App Router searchParams, and duplicate query value normalization.
  • Next.js request-handler tests cover sanitized context forwarding and existing-response preservation.
  • Next.js ESR tests cover plain Request, NextRequest, cookie reads, cookie persistence, and persistence cleanup.
  • React Web provider tests cover server Optimization state being applied before onStatesReady and before children render.
  • Next.js client tests cover NextjsOptimizationState hydration behavior.

[NT-3568]

@Lotfi-Arif Lotfi Anwar L Arif (Lotfi-Arif) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, they look good in terms of separation, but it was tough to review because it was a big change.

if ((await options.shouldHandleRequest?.(context)) === false) {
return response
}
function applyNextjsOptimizationRequestContext(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When chained after another middleware that already used NextResponse.next({ request: { headers } }) to inject rewritten headers, clearForwardedRequestHeaders() deletes those overrides then rebuilds from the raw incoming request only. Any headers injected by an upstream middleware are silently dropped. The tests only verify preservation of ordinary response headers, not pre-existing request header overrides

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dammit, I changed this several times to keep that from happening.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, and updated the AGENTS file to make sure that functionality doesn't get wiped in another code-reduction pass.

Comment on lines +194 to +197
const page = mergePageContext(
mergePageContext(requestPage ?? forwardedPage, getExplicitPage(explicitPage)),
eventContext?.page,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, eventContext.page takes precedence over the explicit top-level page; is that intended?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this was a bit wrong, and I've also updated headers, cookies, and user agent handling to ensure precedence fits what one would reasonably expect.

…y-parameters

### Summary

This fixes server-side audience matching for rules that depend on URL query parameters. The supported Next.js server optimization path can now populate `context.page` from the current request URL, including query parameters, so a request such as `/?test=true` can evaluate query-based audience rules instead of silently rendering baseline content.

While investigating the bug, the issue turned out to span more than URL parsing. Next.js App Router splits request handling, Server Component rendering, and browser startup across different boundaries. The previous request-handler API mixed request context capture, `page()` execution, consent resolution, and cookie persistence in middleware/proxy code, which made it hard for Server Components to reliably receive page URL context without duplicating work. This PR separates those responsibilities and documents the supported integration paths.

### What changed

- Added shared page-context creation in Core so request URLs are normalized into SDK-compatible `page.path`, `page.search`, `page.query`, `page.referrer`, and `page.url`.
- Updated the Next.js server helpers to derive page context from:
  - direct request objects,
  - sanitized proxy/middleware-forwarded request URL headers,
  - explicit App Router route state via `createNextjsPageContext()`.
- Replaced the page-producing Next.js request handler with `createNextjsOptimizationContextHandler()`, which only forwards sanitized request context for Server Components.
- Added `@contentful/optimization-nextjs/esr` for edge/request-rendered flows that own both the incoming `Request` and outgoing `Response`, including anonymous ID persistence.
- Added explicit server-to-browser Optimization state handoff through:
  - `serverOptimizationState` on React Web provider/root APIs,
  - `NextjsOptimizationState` for shared App Router layouts where page data is produced below the provider.
- Introduced internal first-party `bridge-support` entry points for controlled state hydration and preview-panel access, replacing public symbol/register-preview wiring.
- Added diagnostics for conflicting explicit page context versus page event properties.
- Updated Next.js SSR and hybrid reference implementations to use the new request-context and state-handoff model.
- Updated package READMEs, Next.js integration guides, and related concepts for consent, profile synchronization, locale handling, and server/stateless interaction tracking.

### Why the scope expanded

A narrow fix that only parsed query strings would not have made the supported Next.js App Router flow reliable. Server Components do not naturally receive the full request URL, and the old middleware helper tried to solve that by performing SDK work at the request layer. That created unclear ownership over consent, page events, cookie persistence, and browser handoff.

This PR makes the boundaries explicit:

- proxy/middleware captures request URL context only;
- Server Components own `getNextjsServerOptimizationData()` and the initial server `page()` call;
- ESR helpers own request/response-based rendering flows;
- React/Next client code receives server-returned Optimization data through an explicit handoff API;
- preview and state hydration use internal bridge support instead of consumer-visible Core internals.

### Test coverage added or updated

- Core stateless page-event tests cover forwarding request page query context.
- EventBuilder tests cover `context.page` construction and explicit page-context precedence.
- Core bridge tests cover first-party bridge access and Optimization data hydration.
- Next.js server tests cover forwarded request URL headers, explicit page options, App Router `searchParams`, and duplicate query value normalization.
- Next.js request-handler tests cover sanitized context forwarding and existing-response preservation.
- Next.js ESR tests cover plain `Request`, `NextRequest`, cookie reads, cookie persistence, and persistence cleanup.
- React Web provider tests cover server Optimization state being applied before `onStatesReady` and before children render.
- Next.js client tests cover `NextjsOptimizationState` hydration behavior.

[[NT-3568](https://contentful.atlassian.net/browse/NT-3568)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great and consistent! ✅

@phobetron Charles Hudson (phobetron) merged commit 9680c05 into main Jun 29, 2026
76 of 77 checks passed
@phobetron Charles Hudson (phobetron) deleted the NT-3568_fix-missing-query-bug branch June 29, 2026 10:25
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