Skip to content

[pull] canary from vercel:canary - #1354

Merged
pull[bot] merged 5 commits into
code:canaryfrom
vercel:canary
Aug 31, 2026
Merged

[pull] canary from vercel:canary#1354
pull[bot] merged 5 commits into
code:canaryfrom
vercel:canary

Conversation

@pull

@pull pull Bot commented Aug 31, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

mischnic and others added 5 commits August 31, 2026 09:46
Followup to #95233

- unskip one client component test since that is actually expected
behavior already, we just need to test the observable behavior instead
- Fix instant insights warnings
- Don't use console.log since that errors when trying to print server
references
## Summary

- Document how dimensions and poster images keep video layouts stable
while media loads.
- Add equivalent sizing guidance for lazy-loaded iframes.

## Verification

- `prettier --check docs/01-app/02-guides/videos.mdx`

<!-- NEXT_JS_LLM_PR -->
This PR removes some unused code from the image optimizer.

- Remove redundant MIME validation
- Remove unreachable fallback error

These used to be necessary until PR
#82118 removed the fallback.

Before 82118:

```js
upstreamType = detectContentType(upstreamBuffer) || imageUpstream.contentType?.toLowerCase().trim()
```

After 82118:

```js
upstreamType = detectContentType(upstreamBuffer)
```

Now there is never a case where upstreamType is invalid since its
already been validated, there is no more fallback.
The documented in-memory handler stored the resolved entry and returned
it from every `get`. A stored stream keeps the request that produced the
entry reachable. The second cache hit also fails, because it reads a
stream that another reader already consumed. The render then reports
"Invalid state: ReadableStream is locked".

The "Handling Streams" section described a second, milder mistake. It
told a handler to tee the stored stream in `get`. One branch goes back
into the store, and the other serves the current read. That pattern
serves every hit correctly. The built-in handler used it until #97941,
so the page described the framework's own implementation. The stored
branch retains the same request, and it also holds the source stream and
its buffered chunks. The size a store accounts for covers none of that.
The page now states that cost, because a reader who thinks of teeing
would otherwise read the rule as inapplicable.

`get` told a handler to drop an entry once it is past `revalidate`.
Next.js compares `timestamp` against `expire` on every read, and it
treats a too-old entry as a miss. It applies the same check to
`revalidate` when the result goes into another server cache. A handler
therefore needs no age check at all. The built-in handler still drops at
`revalidate`, as a deliberate policy for an in-memory cache, and the
page now presents that as a policy instead of a rule.

Something still has to reclaim entries, so the page states that eviction
belongs to the handler. Next.js never deletes from it. An age check at
serve time is no substitute, because it only reaches the keys that a
reader still asks for.

The page now follows an order in which no rule appears after the code
that depends on it. The stream rules sit in one section ahead of the API
reference. `CacheEntry Type` moves above the methods that reference it.
`set` precedes `get`, because a handler writes an entry before it reads
one. The tag and error sections move above the examples.

`types.ts` described `refreshTags` as periodic, and the page described
it as a call before each request. Neither is accurate. It runs once per
request, before the first cache read for its kind. A request that reads
nothing from a handler never calls it.

Neither `types.ts` nor the page documented the `revalidate: -1` signal,
which the built-in handler relies on. A negative value always lies in
the past, so it serves an entry with a stale tag one more time while
Next.js generates a fresh one.

`types.ts` now states these rules as well, so a handler author no longer
has to read the built-in implementation to find them. It also covers the
`pendingEntry` promise, which a handler must not retain either.

`streamFromBuffer` now records why it has to stay a default stream. A
byte stream transfers the buffer of every chunk it receives. That would
detach the buffer an in-memory handler serves many reads from. The
comment in `tiered-cache-handler.ts` about cancelling a teed stream now
says that the backing handler is user-configured, because the built-in
one no longer returns such a branch.

The handler test from #97941 now records a limit of its own. It only
fails on Node 20 and 22, whose `AsyncLocalStorage` attaches the active
store to every promise. CI runs Node 20.9, so a regression fails there.
A local run on a newer Node passes.

Nothing in this change affects runtime behavior.
- Some of them were not forwarding `getExpiration` or `softTags` to
`defaultCacheHandler`
- use-cache-cross-deployment was ignoring softTags and expiration. (This
patch was written by Sol)

---------

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
@pull pull Bot locked and limited conversation to collaborators Aug 31, 2026
@pull pull Bot added the ⤵️ pull label Aug 31, 2026
@pull
pull Bot merged commit 8330e4c into code:canary Aug 31, 2026
14 of 16 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants