Skip to content

fix(headers): preserve config Link headers alongside React preload links - #2791

Open
MaxtuneLee wants to merge 2 commits into
cloudflare:mainfrom
MaxtuneLee:fix/config-link-header
Open

fix(headers): preserve config Link headers alongside React preload links#2791
MaxtuneLee wants to merge 2 commits into
cloudflare:mainfrom
MaxtuneLee:fix/config-link-header

Conversation

@MaxtuneLee

@MaxtuneLee MaxtuneLee commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #2788.

  • treat Link as an appendable response header when applying matching next.config.js headers() rules in the App Router
  • preserve framework-generated React and next/font preload links when a config rule contributes another Link relation
  • add regression coverage at the response-finalizer, development-server, and production-server levels

Problem

App Router response finalization applies matching config headers after React has emitted preload headers. The config-header merger only appended Vary and Set-Cookie; for every other existing header name it skipped the config value. As a result, a React or next/font preload Link caused an unrelated config Link, such as rel="describedby", to disappear from the final response.

Link is a list-valued field, so the configured relation and framework preload can coexist in one comma-combined field or separate fields. Next.js preserves both values.

Fix

Include link in the set of response headers that use Headers.append() during App Router config-header application. The existing precedence rules for singular response headers are unchanged.

The fixture reproduces the reported behavior with:

  • ReactDOM.preload("/agent-test.woff2", ...)
  • next.config.ts setting Link: </llms.txt>; rel="describedby"; type="text/plain"

Both relations are now present in development and production responses.

This change is limited to config-header application. The separate ISR cache provenance issue reported in #2782 is not changed here.

Test plans

Add new finalizer regression test:

Expected: </llms.txt>; rel="describedby"; type="text/plain"
Received: </agent-test.woff2>; rel=preload; as="font"; crossorigin=""; type="font/woff2"

After the fix, the following checks pass:

  • vp test run tests/app-rsc-response-finalizer.test.ts tests/request-pipeline.test.ts
  • vp test run tests/app-router-next-config-dev.test.ts
  • vp test run tests/app-router-production-server.test.ts -t "preserves config Link headers alongside React preload links"
  • vp test run tests/routing.test.ts tests/route-sorting.test.ts
  • vp check

@pkg-pr-new

pkg-pr-new Bot commented Aug 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@2791
npm i https://pkg.pr.new/create-vinext-app@2791
npm i https://pkg.pr.new/@vinext/types@2791
npm i https://pkg.pr.new/vinext@2791

commit: c740a4c

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared c740a4c against base 3c5cea4 using alternating same-runner rounds. Next.js was unchanged and skipped.

1 improved · 0 regressed · 5 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 134.6 KB 134.6 KB ⚫ +0.0%
Client entry size (gzip) vinext 122.1 KB 122.2 KB ⚫ +0.0%
Dev server cold start vinext 3.00 s 2.94 s 🟢 -2.1%
Production build time vinext 3.19 s 3.17 s ⚫ -0.5%
RSC entry closure size (gzip) vinext 113.8 KB 113.9 KB ⚫ +0.0%
Server bundle size (gzip) vinext 191.4 KB 191.4 KB ⚫ +0.0%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

@MaxtuneLee
MaxtuneLee force-pushed the fix/config-link-header branch from 913d4c8 to 00d8062 Compare August 4, 2026 05:20
@MaxtuneLee
MaxtuneLee marked this pull request as ready for review August 4, 2026 05:44
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.

App Router: next.config Link header is dropped when a preload Link exists

1 participant