Skip to content

[pull] canary from vercel:canary - #1343

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

[pull] canary from vercel:canary#1343
pull[bot] merged 9 commits into
code:canaryfrom
vercel:canary

Conversation

@pull

@pull pull Bot commented Aug 27, 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 : )

lazerg and others added 9 commits August 26, 2026 16:26
When a client disconnects mid-stream, `pipeNodeReadableToNodeResponse`
destroys the readable React is piping into. React sees its destination
close before the render ended and aborts with a generic `Error("The
destination stream closed early.")`, which `onError` has no way to
distinguish from a real render error, so it ends up in `onRequestError`
as a server render error.

The Web streams path doesn't have this problem: cancelling the readable
passes the `ResponseAborted` reason straight through to React. This does
the same for the Node path by aborting the pipeable with that reason as
soon as the passthrough is destroyed early, before React's own close
handler runs. Genuine render errors still go through untouched.

Fixes #96704

---------

Co-authored-by: Jude Gao <jude.gao@vercel.com>
Reverts #97875 and bumps `sharp` from `^0.35.3` to `^0.35.4`,
re-enabling AVIF image optimization.


`minimumReleaseAge` interplay (verified with pnpm 10.33.0 and 11.22.0 in
a standalone repo with a `file:` dependency declaring sharp as an
optional dependency):

- sharp 0.35.4 was published on 2026-08-26 and is younger than this
repo's 48h `minimumReleaseAge`.
- pnpm aborts the whole install with
`ERR_PNPM_NO_MATURE_MATCHING_VERSION` when no in-range release satisfies
the age gate. This applies to `optionalDependencies` (no warn-and-skip)
and through `file:` dependencies alike.
- The gate also covers the `@img/sharp-*` binary packages sharp 0.35.4
depends on, so excluding only `sharp@0.35.4` is not enough.
- `pnpm-workspace.yaml` therefore exempts `sharp@0.35.4` and
`@img/sharp-*` (pnpm does not allow version qualifiers on name patterns
in this setting). Both entries can be dropped once 0.35.4 is older than
48 hours.
- Downstream users who configure their own `minimumReleaseAge` will hit
the same hard install failure when installing a release with this bump
until 0.35.4 ages out, unless they add the same excludes. Keeping
`^0.35.3` is not a safer alternative: age-gated installs would then
silently resolve the vulnerable 0.35.3.

Part of https://linear.app/vercel/issue/VOC-34654/

---------

Co-authored-by: Steven <steven@ceriously.com>
- latest resolved to vercel@59.6.2.
- It loads `@vercel/node@8.1.0` -> `@vercel/static-config@3.4.2`.
- `@vercel/static-config` is CommonJS and calls `require("oxc-parser")`.
- `oxc-parser@0.121.0` is ESM-only and requires Node `^20.19.0 ||
>=22.12.0`.
- CI is running Node 20.9.0, which cannot synchronously
- Fixes #82357
- Closes #96985

### What?

`fetchInternalImage()` only rejects the internal response when
`statusCode` is falsy, and `MockedResponse` defaults it to `200`, so
nothing really gets rejected there. A `307` coming from a `redirects()`
entry in `next.config`, or a `404` for a path that isn't there, both
count as a successful image fetch.

That body then reaches `detectContentType()`, which returns `null`, and
all you get is:

```
The requested resource isn't a valid image for /path/to/image.png received null
```

Nothing in that line points back at the response that actually came in,
so a redirect looks exactly like a corrupt image.

### Why?

`fetchExternalImage()` right above already covers this with `if
(!res.ok)` and logs `res.status`. The internal path is the only fetch in
the file without that check, so the same failure gets reported at the
fetch for remote images, and three steps later as "not a valid image"
for local ones.

### How?

The guard now rejects anything outside the 2xx range, which is what
`res.ok` means, and the log line carries the status next to the href.
The thrown `ImageError` message stays generic, same as the external
path, so nothing new shows up in the HTTP response body.

Because that throw passes `mocked.res.statusCode` into `ImageError`, a
non-2xx internal response now reports its own status instead of always
landing on `400`. `ImageError` still maps anything under 400 to `500`,
so a redirect answers `500` and a `404` stays `404`. That is what
`fetchExternalImage()` has always done, and it lines the two up: a
missing local image now returns `404` just like a missing remote one.

Three assertions in `test/e2e/image-optimizer/util.ts` shift because of
that:

- `should not forward cookie header`: `/api/conditional-cookie` answers
`401` when the cookie is missing, so `400` becomes `401`
- `should error if the image file does not exist`: `/does_not_exist.jpg`
returns `404` now, with the internal response message
- `should error if the resource isn't a valid image`: this one requested
`/test.txt`, but the fixture in `public/` is `text.txt`, so it was
quietly exercising a missing file instead of a non-image file. Pointed
at the file that exists, it keeps its original `400` and finally tests
what its name says

Two unit tests cover the new guard, one `307` and one `404`. Every other
test in that file sets `statusCode = 200`, so they are untouched.

#96985 is open against the same report with a different angle: it
threads the status through `ImageUpstream` so the existing "isn't a
valid image" log can print it. This one stops the request at the fetch,
so the redirect never reaches the optimizer at all. Whichever one fits
better, feel free to close mine.

### Verification

- `jest test/unit/image-optimizer/`: 139/139, and the two new tests fail
against a build without the guard
- `pnpm test-dev
test/e2e/image-optimizer/content-disposition-type.test.ts`: 97/97
- `pnpm test-start
test/e2e/image-optimizer/content-disposition-type.test.ts`: 97/97
- `prettier --check` clean on the three changed files

<!-- NEXT_JS_LLM -->

Co-authored-by: Steven <steven@ceriously.com>
CSRBailout error is the use case for `ReactDOM.browser()`. Replace
behind an experimental flag.
This PR replaces the bailout error used in next/dynamic ssr: false.
…flag (#96843)

Stacked on #96826.

This PR replaces the bailout error used by `useSearchParams` with
`ReactDOM.browser()` behind the experimental flag.

### How?

Move the bailout behind a browser-safe module boundary, use
`ReactDOM.browser()` when enabled, and preserve existing behavior
otherwise.

<!-- NEXT_JS_LLM -->
…lag (#96844)

Stacked on #96843.

This PR replaces the bailout error used to abort resumed renders with
`ReactDOM.browser()` behind the experimental flag.

### How?

Pass the flag to the App Router runtime, use `ReactDOM.browser()` as the
abort reason when enabled, and preserve existing behavior otherwise.

<!-- NEXT_JS_LLM -->
#97936)

### What?
`getModuleId()` returns `string | number | null`, and `0` is a valid id.
The`ConcatenatedModule` fallback guards it with a truthiness check, so
when the concatenated module gets id `0` the client entry is left out of
the client reference manifest.

### Why?
The
[code](https://github.com/vercel/next.js/blob/d09816fdaf2fef7a23751de75a9dde6bb7c2403b/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts#L516)
right above it already checks `modId !== null`. The two checks sit a few
lines apart and disagree about whether `0` is a valid id, which looks
like an oversight. When it happens the build fails with `Could not find
the module ... in the React Client Manifest`, which doesn't point at the
cause.

Repro: https://github.com/jgruica/next-concat-modid-zero (needs
`--webpack` plus `concatenateModules: true`, so it's rare in practice)

### How?
One line in `flight-manifest-plugin.ts`: `if (concatenatedModId)`
becomes `if (concatenatedModId !== null)`. `null` is still skipped, so
nothing elsechanges.

Co-authored-by: Marcos Hernanz <96699542+marcoshernanz@users.noreply.github.com>
@pull pull Bot locked and limited conversation to collaborators Aug 27, 2026
@pull pull Bot added the ⤵️ pull label Aug 27, 2026
@pull
pull Bot merged commit d4b7cc1 into code:canary Aug 27, 2026
6 of 7 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.

5 participants