fix(app-render): stop prerendered Link headers from accumulating on revalidation (#100960)
### What?
With `cacheComponents: true`, a `Link` header set in `next.config.js`
`headers()` (or by middleware) is duplicated on ISR pages that also emit
a React preload `Link` (e.g. an `<img fetchPriority="high">`), and gains
one more copy on every background revalidation:
```
req 3 Link: </llms.txt>; rel="llms-txt"
Link: </llms.txt>; rel="llms-txt",</llms.txt>; rel="llms-txt",</hero.png>; rel=preload; as="image"; fetchpriority="high"
req 4 Link: </llms.txt>; rel="llms-txt"
Link: </llms.txt>; rel="llms-txt",</llms.txt>; rel="llms-txt",</llms.txt>; rel="llms-txt",</hero.png>; rel=preload; ...
...
```
An on-demand render (cache `MISS`) of a page not prerendered at build
time also returns both the config `Link` and the preload `Link` twice.
### Why?
`prerenderToStream` handles React's `onHeaders` by appending each value
to the live response and then storing `res.getHeader(name)` in
`metadata.headers`, which becomes the APP_PAGE cache entry's `headers`.
That snapshot includes values the render did not produce:
- the `Link` from `headers()` in next.config.js, already on the response
before rendering;
- during a background revalidation, the `Link` values the serving path
replayed from the previous cache entry onto the same response.
`app-page-runtime` appends the cache entry's headers to every response
after the config headers are set, so each regeneration stores, and later
serves, one more copy. On a `MISS` the render has already written the
headers to the response, and the same values are appended again from
`metadata.headers`.
### How?
`prerenderToStream` now records only the values it produces (React's
`onHeaders` output and the redirect `location`) in `metadata.headers`,
and no longer writes them to the response. Every consumer of a prerender
result already applies `metadata.headers` to the response
(`app-page-runtime` for fresh renders and cache hits, the export worker
at build time), so the headers reach the client exactly once. A `Link`
from middleware or next.config.js is still merged with React's preload
`Link` (#73431), because the cached value is appended to the response,
not set over it.
`renderToStream` (the dynamic path) is unchanged. It writes `onHeaders`
output to the response only and never stores it in `metadata.headers`,
so it has no cache entry to accumulate into.
Added `test/production/app-dir/cache-components-link-header`, which
configures a `Link` in `headers()`, renders a `'use cache'` page with a
1s `revalidate` and a high-priority image, and checks the config `Link`
and the preload `Link` appear exactly once on every response across four
revalidations, for a build-time prerendered page, a prerendered dynamic
param, and an on-demand dynamic param. Without the change it fails on
webpack (the config `Link` count climbs 1, 2, 3, 4, 5, and the `MISS`
response has 2 of each). With the change it passes on both webpack and
Turbopack.
One behavioural difference: the segment-prefetch cache branch returns
before the cached headers are replayed, so an RSC segment-prefetch
response that happens to trigger a fresh render no longer carries the
preload `Link`. Such responses have no use for a preload.
Standalone reproduction:
https://github.com/linyiru/next-cache-components-link-header-repro
Fixes #100959
<!-- NEXT_JS_LLM -->