ISR Background Revalidation Stores Stale Link Header, Dropping Early Hints For PPR Routes
During background revalidation of partially prerendered (PPR) routes, Next.js reads the Link header from the already-finished response object instead of from the values React passed to appendHeader, causing Flight-triggered regenerations to store no Link header and lose font preload hints.
In server/app-render/app-render.tsx, the appendHeader wrapper calls res.appendHeader(name, value) then setMetadataHeader(name), which reads metadata.headers[name] = res.getHeader(name). On Node 26, calling setHeader on an already-sent ServerResponse silently does nothing, so res.getHeader returns the stale/empty header from the finished response, not the newly appended value. Thus the regenerated ISR entry loses the early-hint Link header for Flight requests.
1. Clone https://github.com/vicanera/next-isr-regeneration-drops-link-hints and run npm ci && npm run build && npm run repro with Next 16.3.5.
2. After build, confirm .next/server/app/index.meta contains the font Link header.
3. Trigger a document request: wire=font:1, stored after=font:1. Trigger an RSC or prefetch request: wire=font:0, stored after=font:0, and no later regeneration restores it.
4. With PROXY_LINK=1 node repro.mjs --triggers document,document,rsc, document triggers replace/append proxy header, RSC trigger stores only proxy header, dropping font hint.
Updated setMetadataHeader to accept the value passed to appendHeader and accumulate it in metadata.headers independently of the underlying response object. appendHeader now passes the value, so the stored ISR metadata reflects the hints React emitted for the new render, even when the original ServerResponse has already been sent.
Edge Case Audit
This change affects all appendHeader calls during prerendering; it accumulates values in metadata independently of res. Ensure downstream consumers expect arrays for multi-value headers. Rollback if duplicate headers appear unexpectedly. If Node changes res.appendHeader to throw on finished responses, setMetadataHeader will not execute because it is called after; consider wrapping in try/finally. Test with blocking revalidation, build export, and proxy-set headers to verify no regressions.