499 Commits

Author SHA1 Message Date
Tim Neutkens cbf0cef687 Enable TypeScript CLI by default (#96497)
## Summary

Enable the project-local TypeScript CLI checker by default during `next
build`, while preserving `experimental.useTypeScriptCli: false` as an
opt-out to the TypeScript compiler API. Update the TypeScript 7
guidance, diagnostics, tests, and documentation to match the new
default.

## Verification

- `pnpm exec jest --runTestsByPath test/unit/isolated/config.test.ts`
- Pre-commit lint-staged checks passed (Prettier and ESLint)
- Not run: `pnpm test-start-turbo
test/production/app-dir/typescript-cli/typescript-cli.test.ts` (isolated
fixture dependency installation was blocked by unavailable npm registry
access)
- Not run: `pnpm --filter=next types` (repository has pre-existing
unrelated TypeScript errors)

<!-- NEXT_JS_LLM -->
2026-08-03 18:10:51 +02:00
Hendrik Liebau ccd47cfe53 Warn that experimental.useCache is deprecated (#96448) 2026-07-31 20:26:10 -04:00
dan be1424f3dc Make retry stop when the time it was given is up (#96354)
How long do you expect this to wait at worst (before failing)?

```js
await retry(async () => {
  expect(await browser.elementById('never-appears').text()).toBe('x')
}, 15_000)
```

After this PR, the answer is 15 seconds. (Well, technically, 15 seconds
+ whatever last attempt is, so 20.)

But before this PR, the answer was 3 minutes.

## What?

There's a bug (IMO) in `retry`. Instead of waiting for the time you give
it, it counts tries.

For example, `retry(fn, 15_000)` reads as "keep trying for 15 seconds".
However, what it actually was doing is divide 15000 by the 500ms
interval and try 30 times, sleeping 500ms in between every try.

This is a problem because **it wasn't measuring the stall time of each
try itself.**

When a try is cheap, it doesn't matter. But a try that looks for an
element in the browser isn't cheap: if the element isn't there, the
selector waits 5 seconds before giving up.

In the example above, each try sits in the selector for its full 5
seconds, and there are 31 of these intervals, plus the 15 seconds of
sleeping in between. This ends up waiting 170103ms, i.e. three minutes
for a wait that says 15 seconds.

Jest kills the test at its own timeout long before that, and the actual
error gets thrown away. What's left is `Exceeded timeout of 120000 ms`,
which doesn't say which assertion never came true. In the one I measured
it was `page.waitForSelector: Timeout 5000ms exceeded`. My agent lost an
hour trying to make sense of this twice now.

## Fix

We count time now. After the fix, the retry watches the clock and stops
when its time is up. It still always makes at least one try. The
interval no longer has to divide the duration, because nothing counts
steps any more. If the last try is slow, we're gonna wait for that last
try to end (so the timeout is still not *strictly* respected) so that we
can show the error in the correct test.

Cheap waits behave exactly as before. In my local run, a try that had to
repeat cost 251ms at the median, 459ms at the worst. So the only calls
this changes are ones that were already failing, and those now fail with
their own error instead of a test timeout. One flaky dev test that used
to die at 120 seconds with nothing to show now fails in 19.5 seconds
saying `Failed to retry within 15000ms (waited 16022ms)`, next to nine
tests that take 2.2 seconds each.

I added some regression tests. Since it's a test helper, a unit test
seemed fine.

## What this means for existing tests

The catch is that a call whose tries are slow now gets less time than it
used to, and this helper has 2110 call sites, so CI is the real check.
It found one file: the hash scroll tests in `navigation.test.ts` wait
with the default 3 seconds, and each try there costs 150-300ms on a
runner, so they were really getting about 5. They need more than 3, so
they failed on two dev shards. I gave them 10.

If this is green, we can merge it in isolation from the rest of the
stack.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 21:42:56 +01:00
Steven 7c19c7c06a fix(next/image): improve test coverage for unsupported image types (#96301)
We were missing some test coverage for image optimization. This PR adds
tests for many missing image formats.

Additionally, this PR also ensures the sharp encoders match that
allowlist from detectContentType() such that future versions of sharp
that enable new encoders by default are not automatically enabled in
next/image.

Practically, there is no change to the end user because
detectContentType() runs before sharp so that is the true allowlist, but
this gives us a little more control to align the sharp allowlist as well
(since sharp can release new features in semver minor).
2026-07-28 14:05:06 -04:00
Tim Neutkens 1de0812714 Add option to control dev memory threshold restarts (#96144)
## Summary

Adds `experimental.devMemoryThresholdRestart`, defaulting to `true`, so
projects can set it to `false` to opt out of the development server
restart that occurs when V8 heap usage exceeds 80%. The option has no
effect on `next start`.

## Verification

- Focused config-loading and memory-threshold unit suites passed
- `pnpm --filter=next build` completed JavaScript compilation, then
failed on pre-existing `sharp` and `@vercel/detect-agent` declaration
errors unrelated to this change

<!-- NEXT_JS_LLM -->
2026-07-28 13:22:22 -04:00
dan e2dce63f3f [test] Unflake more tests (#96081)
- `detectContentType` test was too aggressive about asserting completion
in 1ms, let's give it several tries.
- We weren't correctly setting up pnpm overrides in tests, which breaks
things whenever canary comes out.
2026-07-27 20:54:10 +01:00
Pete Hunt d2ee326a0b fix(sandbox): release one-shot timeout ids after they run (#96161)
### What?

Fire-and-forget `setTimeout` calls in the Edge runtime sandbox (e.g.
from middleware) are tracked forever by Next.js's sandbox
`TimeoutsManager`, even after the timeout has run. In a long-lived
standalone/self-hosted server this makes tracked timeout resources grow
monotonically, producing step-like heap growth and eventual OOM.

### Why?

`TimeoutsManager.add()` pushes each timeout id into a `resources` array.
When a one-shot timeout fires naturally, `webSetTimeoutPolyfill`'s
`finally` clears the underlying Node timer (the workaround for
nodejs/node#53335) but never removes the id from the manager. The id is
dropped only if user code calls `clearTimeout(id)` or the whole module
context is torn down (`clearModuleContext`, introduced in #57235). The
natural-completion path — module context stays alive, timeout finishes
on its own — was missing, so ids accumulate for the lifetime of the
process.

### How?

- `TimeoutsManager.create()` now wraps the callback so the timeout
untracks its own id once it has run. The Node-timer workaround and the
`this`/args binding in `webSetTimeoutPolyfill` are preserved.
- Refactored the base `ResourceManager.remove()` into `untrack()` (stop
tracking) + `destroy()`, so natural completion can release tracking
without a redundant `clearTimeout`.
- Intervals are intentionally unchanged: they fire repeatedly and must
remain tracked until `clearInterval`/`removeAll`.
- Added a unit test covering natural release, no monotonic growth across
repeated rounds, the `clearTimeout`/`remove` path, callback `this`/args
binding, and that intervals stay tracked.

Verified: `pnpm --filter=next types` clean, prettier/eslint clean, and
the new unit test fails on the current behavior (reverting only the
release logic) and passes with the fix.

Fixes #95094

---

**Update:** this branch now lands @berry95's commit from #94794 first
(`Set`-based resource tracking, which also makes `untrack`/`remove`
O(1)), with the fix rebased on top.

---------

Co-authored-by: Berry Kim <wlsdud40@gmail.com>
Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2026-07-25 19:42:17 +01:00
Sebastian "Sebbie" Silbermann 025bf4a5f7 fix(incremental-cache): byte-exact fetch cache key for binary bodies (#96008)
https://github.com/vercel/next.js/security/advisories/GHSA-4633-3j49-mh5q

Co-authored-by: Karim Rahal <karim@karimrahal.com>
2026-07-21 18:46:44 +02:00
Sebastian "Sebbie" Silbermann 93cb908914 fix(next/image): improve performance of detectContentType() (#96006)
https://github.com/vercel/next.js/security/advisories/GHSA-q8wf-6r8g-63ch

Co-authored-by: Steven <steven@ceriously.com>
2026-07-21 18:46:44 +02:00
Tim Neutkens 3bb780e7d6 Keep the request body a plain Readable after middleware so Readable.toWeb() doesn't hang (#95607)
Recreation of https://github.com/vercel/next.js/pull/95370

### What's the problem?

A `POST` (or `PUT`/`PATCH`) request that passes through middleware
returning `NextResponse.next()` hangs indefinitely when the downstream
handler reads the body via Node's `Readable.toWeb()`. The request never
completes and eventually times out.

Reproduction: https://github.com/abir-taheer/next-js-readable-stream-bug

### Root cause

When middleware runs, `runMiddleware` clones the request body and later
calls `finalize()`, which grafts the buffered stream back onto the
original `IncomingMessage` via `replaceRequestBody()`
(`packages/next/src/server/body-streams.ts`). `replaceRequestBody`
copies the buffered stream's enumerable properties onto the request.

The buffered stream (`p2`) was a `PassThrough` — a `Duplex` — so its
writable-side internals (`_writableState` plus the enumerable `Writable`
methods like `write`/`end`) were copied onto the `IncomingMessage`.
Because that `_writableState.finished` is `false`, Node stream utilities
that inspect it — including `Readable.toWeb()`, which uses
`finished()`/end-of-stream detection — treat the request as a still-open
writable stream and wait forever.

`NextResponse.rewrite()` is unaffected (it builds a new internal request
and skips this path), and `GET`/`HEAD` requests are fine because there
is no body to clone.

### The fix

`p2` is only ever fed with `.push()`, so it never needs a writable side.
Making it a plain `Readable` instead of a `PassThrough` keeps the
finalized request a pure `Readable`, so `Readable.toWeb()` (and any
other duck-typing based on `_writableState`) behaves correctly. No
behavior change for the existing consumers, which only read the stream.

### Testing

Added `test/unit/body-streams.test.ts`, which drives the real clone →
`finalize()` flow and asserts the finalized request:
- is no longer writable (`_writableState` is `undefined`), and
- is fully consumable via `Readable.toWeb()` (this hangs before the
fix).

Fixes #95335

<!-- NEXT_JS_LLM_PR -->

Co-authored-by: UditDewan <udit.dewan21@gmail.com>
Co-authored-by: Baradhan-Madhu <26barum@gmail.com>
2026-07-10 15:55:29 +02:00
Tim Neutkens a249dcbcee (TypeScript 7 Support) Add experimental TypeScript CLI backend (#95639)
## Summary

- add `experimental.useTypeScriptCli` so projects can explicitly run
their local `tsc` during `next build`, including TypeScript 7 while the
legacy JavaScript API is unavailable
- preserve the TypeScript API backend as the default, with TypeScript
6-compatible dependency installation and actionable TypeScript 7
migration guidance
- load effective compiler metadata through `tsc --showConfig`, preserve
inherited path origins, stream native diagnostics, redirect incremental
state, and forward worker termination to the compiler process tree
- document the experimental behavior and cover TypeScript 6/7, both
bundlers, raw diagnostics, full-project checking, dependency selection,
and cleanup

## Demo

```
pnpm build

> cna@0.1.0 build /Users/timneutkens/projects/sandbox/cna
> next build

▲ Next.js 16.3.0-canary.81 (Turbopack)
✓ Running next.config.ts took 23ms
- Cache Components enabled
- Experiments (use with caution):
  ✓ useTypeScriptCli

  Creating an optimized production build ...
✓ Compiled successfully in 3.9s
  Running TypeScript ...
app/page.tsx:8:3 - error TS2322: Type 'string' is not assignable to type 'number'.

8   return count++ + "abc";
    ~~~~~~


Found 1 error in app/page.tsx:8

Failed to type check.

Next.js build worker exited with code: 1 and signal: null
 ELIFECYCLE  Command failed with exit code 1.
```


## Verification

- `pnpm test-start-turbo
test/production/app-dir/typescript-cli/typescript-cli.test.ts`
- `pnpm test-start-webpack
test/production/app-dir/typescript-cli/typescript-cli.test.ts`
- `pnpm test-start-turbo
test/production/ci-missing-typescript-deps/index.test.ts`
- `pnpm test-dev-turbo
test/development/typescript-native-preview/index.test.ts`
- `pnpm test-start-turbo test/production/typescript-basic/index.test.ts`

<!-- NEXT_JS_LLM_PR -->
2026-07-09 18:43:12 -07:00
Sukka e37c6d636c Enhance ESLint rule no-location-assign-relative-destination (#93057)
- Implements `location` scope check (suggested by @bgw in
https://github.com/vercel/next.js/pull/92900#discussion_r3107167621),
ensuring `location` is a true global variable before reporting.
- Implements basic variable tracking (suggested by @icyJoseph in
https://github.com/vercel/next.js/pull/93057#issuecomment-4281583840)

---------

Co-authored-by: Benjamin Woodruff <benjamin.woodruff@vercel.com>
2026-06-29 13:45:49 -07:00
Andrew Clark f5af284527 Add global config to enable Partial Prefetching (#94448)
Adds a global config option called `partialPrefetching` that changes the
behavior of `<Link prefetch={true}>`.

When Partial Prefetching is enabled, only Cache Components are included
in a prefetch response. Dynamic data is omitted. This is the same idea
as Partial Prerendering for initial page loads, now applied to client
navigations.

In the initial Cache Components release, Partial Prefetching was enabled
for any Link that did not explicitly set a `prefetch` prop. However, if
the `prefetch` prop was set to `true`, then the Link retained the old
behavior where the _entire_ page was prefetched, including dynamic data.
This was to ease the migration path for existing apps that already
relied on dynamic data being included in the prefetch.

For new apps that have Cache Components enabled from the start, it's
suggested to also enable `partialPrefetching`.

This PR also adds a per-segment opt-in: a route segment can set
`unstable_prefetch = 'partial'` to enable Partial Prefetching just for
that segment, so apps can migrate gradually before enabling it globally.

<!-- NEXT_JS_LLM_PR -->
2026-06-07 21:33:09 -07:00
Aurora Scharff a0c6a6140e instant: enable navigation validation by default (#94312)
### What?

Flips the default `experimental.instantInsights.validationLevel` from
`'manual-warning'` to `'warning'` so Cache Components apps get
instant-navigation validation across all pages by default.

### Why?

`'manual-warning'` only validates pages that explicitly export
`unstable_instant`, so apps see nothing unless they opt in. `'warning'`
is what users have been turning on manually to actually see the feature
(`v0`, `vercel-site`).

### Test coverage

- Unit: `instant-config-normalization.test.ts` pins the framework
default at `'warning'`.
- Integration: new `instant-validation-level-default/` fixture (no
`instantInsights` in config) asserts implicit dev validation fires, and
that build is unaffected.

### Collateral test fixtures

Tests whose intent is unrelated to instant validation
(router-autoscroll, owner-stack, hmr-iframe, next-image,
server-source-maps, etc.) now hit new redboxes/console warnings because
their fixtures incidentally use dynamic data. Each opts out with
`experimental: { instantInsights: { validationLevel: 'manual-warning' }
}` in `next.config`.

### Open question

With no `'off'` / `'info'` tier today, silencing Insights after this
change requires setting `validationLevel: 'manual-warning'`. Is that
acceptable? Should we inform of this anywhere?

<!-- NEXT_JS_LLM_PR -->
2026-06-03 01:26:11 +02:00
Tim Neutkens b92cf40ee6 Enable Node streams by default (#94311)
### What?

Defaults `experimental.useNodeStreams` to `true` while keeping the
experimental config flag and explicit `false` opt-out available. Removes
redundant dedicated node-stream CI jobs because the ordinary path
exercises Node streams after this change.

Depends on the standalone test compatibility update in #94347, which
intentionally remains outside the Node streams PR stack.

### Why?

Node streams can be exercised through the standard Node.js App Router
test path once they are the default, allowing a smaller rollout before
the larger follow-up that removes the flag and obsolete plumbing.

CI exposed one necessary compatibility fix for this intermediate state:
projects with a `next.config.*` received the new default after raw
experimental-feature processing, leaving the runtime stream selector on
the web implementation while compiled app code selected Node stream
helpers.

### How?

- Set `defaultConfig.experimental.useNodeStreams` to `true`.
- Synchronize `__NEXT_USE_NODE_STREAMS` from the fully resolved config,
including cached and standalone resolved configs, so defaults and
adapter modifications are reflected at runtime.
- Preserve the current public config/schema and explicit `experimental:
{ useNodeStreams: false }` opt-out; edge bundles continue to define the
stream flag as `false`.
- Remove the dedicated node-stream test jobs and now-unused manifest;
the affected streaming assertion compatibility changes are isolated in
#94347.

### Verification

Run before extracting the prerequisite assertion changes into #94347:

- `pnpm --filter=next types`
- `pnpm --filter=next build`
- `IS_WEBPACK_TEST=1 __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true pnpm
test-start-webpack
test/e2e/app-dir/ppr-root-param-fallback/ppr-root-param-fallback.test.ts`
- `IS_TURBOPACK_TEST=1 TURBOPACK_BUILD=1
__NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true pnpm test-start-turbo
test/e2e/app-dir/ppr-root-param-fallback/ppr-root-param-fallback.test.ts`
- `IS_WEBPACK_TEST=1 __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true pnpm
test-start-webpack
test/e2e/app-dir/use-server-inserted-html/use-server-inserted-html.test.ts`
- `pnpm test-dev-webpack
test/e2e/app-dir/ppr-root-param-fallback/ppr-root-param-fallback.test.ts
test/e2e/app-dir/use-server-inserted-html/use-server-inserted-html.test.ts`
- `pnpm test-dev-turbo
test/e2e/app-dir/ppr-root-param-fallback/ppr-root-param-fallback.test.ts
test/e2e/app-dir/use-server-inserted-html/use-server-inserted-html.test.ts`
- `pnpm test-start-webpack
test/production/app-dir/use-node-streams-env-precedence/use-node-streams-env-precedence.test.ts`
- `pnpm test-start-webpack test/e2e/app-dir/app/standalone.test.ts`

<!-- NEXT_JS_LLM_PR -->
2026-06-02 21:34:22 +02:00
Josh Story 079df0f6f4 Enable rootParams by default (#93863)
rootParams is now available by default. the flag is removed.

There are still intentional limitations. For instance rootParams cannot
be used in route handlers and Server Actions. The feature will be
expanded with some support for this in the future.
2026-05-19 09:45:55 -07:00
Tim Neutkens 8141dcf12e Convert test/integration to isolated tests (#93247)
### What?

Converts every test under `test/integration/` to an isolated test
running through `nextTestSetup` (under `test/e2e/`, `test/production/`,
`test/development/`, or `test/unit/`), then deletes `test/integration/`
along with the legacy CI orchestration that was specific to it.

- `test/integration/` removed entirely (~327 test suites)
- New isolated suites added across the existing folders:
  - `test/e2e/` — 175
  - `test/production/` — 130
  - `test/development/` — 43
  - `test/unit/` — 1
- `.github/workflows/build_and_test.yml` and `run-tests.js` no longer
have any `integration` branches
- `nextTestSetup` gained a `baseUrl` option on `next.browser()` so a
small number of tests that drive their own proxy/static-export server
can keep using `next.browser(...)` instead of importing `next-webdriver`
directly

### Why?

`test/integration/` predated `nextTestSetup` and ran tests directly
against the source checkout via custom helpers (`launchApp`,
`nextBuild`, `nextStart`, `runNextCommand`, `webdriver`, `fetchViaHTTP`,
…). Each suite hand-rolled its own dev/start/build orchestration,
fixture mutation, and process management.

The isolated test model used by the rest of the repo gives each suite an
isolated working directory containing a packed `next.tgz` install, a
uniform `next.start()` / `next.build()` / `next.fetch()` /
`next.browser()` API, and the same lifecycle for dev, start, and deploy
modes — so a single set of assertions covers all three. Deploy-mode
skips and per-feature gates are expressed declaratively
(`skipDeployment`, `disableAutoSkewProtection`, `if (skipped) return`)
instead of branching on `process.env`.

Removing `test/integration/` lets us:

- Delete the bespoke orchestration code in the CI workflow and
`run-tests.js`
- Run every converted suite consistently in dev, start, and deploy modes
(where applicable)
- Reproduce every test locally with the same `pnpm
test-{dev,start}-{turbo,webpack}` commands; no separate `integration`
path
- Open the door to running `test/production` against deployments in the
future (the converted suites already declare `skipDeployment` so they
can be flipped on)

### How?

Mechanical conversion per suite, with targeted clean-ups:

1. **Per-suite conversion.** Each
`test/integration/<name>/test/index.test.{js,ts}` was rewritten into a
single `<name>.test.ts` under the right folder based on what the
original exercised:
   - `launchApp` / dev-only assertions → `test/development/`
- `nextBuild` + `nextStart` / start-only assertions → `test/production/`
   - Both → `test/e2e/`
- The one pure jsdom render check (`link-without-router`) → `test/unit/`

2. **API mapping.** Custom helpers were replaced by `nextTestSetup`
equivalents: `launchApp` → `next.start()`, `nextBuild` → `next.build()`,
`runNextCommand` → `next.runCommand`, `fetchViaHTTP` → `next.fetch`,
`webdriver(...)` → `next.browser(...)`. Fixture mutations switched from
raw `fs.writeFile`/`fs.rename` to `next.patchFile` (with the 3-arg
`runWithTempContent` callback when the change has a defined scope) and
`next.deleteFile`.

3. **Deploy-mode handling.** Suites that can't run in deploy mode (use
`patchFile` / `next.build()` / depend on local CLI output) declare
`skipDeployment: true` and early-return on the `skipped` boolean. Suites
where Vercel's edge mutates URLs (`&dpl=`, immutable assets) declare
`disableAutoSkewProtection: true`.

4. **`next.browser({ baseUrl })`.** A handful of tests
(`prerender-export`, `cdn-cache-busting`, `preload-viewport`, both
`react-virtualized` suites) need to drive a separate server (a
static-export server or an `http-proxy` instance) rather than the
Next.js process. Instead of importing `next-webdriver` directly, those
tests now pass `{ baseUrl: <port|url> }` to `next.browser()`. For the
proxy cases, the proxy was moved into `server.js` inside the fixture and
`http-proxy` declared via the `dependencies` option of `nextTestSetup`,
so the test runs with a fully isolated dependency graph.

5. **CI clean-up.** With `test/integration` gone, the `test
integration*` jobs and `integration-tests-manifest`-related logic in
`.github/workflows/build_and_test.yml` were removed, and `run-tests.js`
no longer has the `integration` test-folder branch.

6. **Validation.** The PR was iterated against multiple full CI runs;
the remaining failures on the latest run are pre-existing flakes
(segment-cache 60s `act` timeouts in turbopack-prod) or transient
infrastructure issues unrelated to the conversion.
2026-05-11 13:55:47 +02:00
Tim Neutkens 647d923a3f Cherry-pick ghsa commits to canary (#93614)
## What?

Includes the changes from 16-2.

---------

Co-authored-by: JJ Kasper <jj@jjsweb.site>
Co-authored-by: Josh Story <story@hey.com>
Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com>
Co-authored-by: Benjamin Woodruff <benjamin.woodruff@vercel.com>
2026-05-07 21:57:46 +00:00
Josh Story d86e197728 Support configuring a default instant validation level (#93301)
Adds `experimental.instantInsights.validationLevel` so an app can opt
into instant validation for all routes by default.

There are two public levels
* `manual-warning`: currently the default. this mode won't enable
instant validation except where you add `export const unstable_instant =
true` into your Layouts, Pages, and Default files
* `warning`: This mode will enable instant validation for all Page and
Default files. You can override this with `export const unstable_instant
= false` in Layouts, Pages, and Default files to tune what navigations
are validated.

In addition this change disables build time validation.

There is a now undocumented way to get back to running build validation
with
* `experimental-error`
* `experimental-manual-error`

The build validation isn't ready for users because we need to overhaul
the setup where you provide cookies and other sample values so we can
run validation without a live session like we do in dev.

To further support this the two remaining object properties that are
also not ready for active use, `samples` and `from` are being updated to
`unstable_samples` and `unstable_from` respectively.

We will continue to iterate on build time validation but may remove
these APIs or alter their semantics as we figure out a more ergonomic
way to support this setup.

In anticipation of multiple levels the object form of `instant` can be
provided a level value `warning` or `experimental-error`. this latter
option is similarly undocumented so documented behavior will only cover
`warning` which is generally a synonym of true unless you are activating
error by default
2026-05-06 10:37:50 -07:00
Rishi 5452439f3d fix(next/image): Improve error message for private IP (SSRF) rejections (#91686)
Co-authored-by: Steven <steven@ceriously.com>
2026-05-04 19:15:32 +02:00
Sukka 241ac2d62b New ESLint rule: no-location-assign-relative-destination (#92900)
## For Contributors

### Adding a feature

Adding a new ESLint rule to `eslint-plugin-next` reporting
`location.href = [relative URL]` or `location.assign([relative URL])`
usage.

For some reason, some LLM models prefer this incorrect pattern, and
avoid `redirect()` or `useRouter().push()` at all costs. But at least
many of them do respect ESLint, by adding a new rule we can enforce
correct behavior.

The error documentation and unit test cases are also added.

---------

Co-authored-by: Joseph <sephxd1234@gmail.com>
Co-authored-by: Benjamin Woodruff <benjamin.woodruff@vercel.com>
2026-04-19 23:25:35 +00:00
Steven 4e08dc7bce fix(next/image): ensure images.maximumResponseBody applies to local images too (#92920)
In a previous PR https://github.com/vercel/next.js/pull/88183, we added
`images.maximumResponseBody` but in only applied to external images.

This PR ensures the same config also applies to internal images.
2026-04-16 16:36:50 -07:00
Niklas Mischkulnig 56e5acc099 webpack: fix swcPlugins with relative paths (#92770)
Webpack didn't correctly load relative paths for `swcPlugins`.

https://github.com/vercel/next.js/pull/92579 will add a test
2026-04-15 12:04:48 +02:00
Niklas Mischkulnig 7ac71949ab Turbopack: switch from immutable token to path prefix (#92164) 2026-04-08 22:54:08 +02:00
Ben Gubler 46e2114ea2 feat: generate root params types (#91019)
## Summary

Generate type definitions for `next/root-params` from App Router root
layouts.

This adds a `root-params.d.ts` file under `.next/types` during build,
`next dev`, and `next typegen`, so imports from `next/root-params` get
accurate autocomplete and type checking.

## What Changed

- Collect root param metadata from layout routes in the route types
manifest
- Generate `.next/types/root-params.d.ts` for `next/root-params`
- Emit root param types in `next build/dev/typegen`
- Infer `undefined` only when a param is not present in every root
layout
- Model mixed root param shapes correctly as unions
- For example, `string | string[] | undefined` when the same param name
appears with different shapes across roots
- Remove stale `root-params.d.ts` when the feature is disabled or there
are no root params

## Why

`next/root-params` already works at runtime, but its types were not
tailored to the current app.

This change closes that gap by generating declarations from the app's
actual root layouts. Required params stay required for single-root apps,
while multi-root apps correctly expose params that may be missing or
have mixed segment shapes.

## Testing

- Added focused unit tests for root param type generation
- Added a typecheck e2e test for generated `root-params.d.ts`
2026-04-08 19:04:12 +02:00
Jason Tan 2bf38b0115 feat: add experimental.swcEnvOptions for SWC preset-env configuration (#92272)
### What?

Add `experimental.swcEnvOptions` to expose SWC's preset-env `env`
configuration options — including `mode`, `coreJs`, `include`,
`exclude`, `skip`, `shippedProposals`, `forceAllTransforms`, `debug`,
and `loose`.

### Why?

Currently Next.js only passes `env.targets` (derived from browserslist)
to SWC for **syntax downleveling**, but does not expose the polyfill
injection capabilities that SWC already supports. This means:

- Users who need automatic core-js polyfills (e.g.
`Array.prototype.at()`, `Promise.withResolvers()`, `Set` methods) have
no built-in way to get them.
- The only workarounds are importing `core-js` globally (which bloats
bundles significantly) or ejecting to Babel with `useBuiltIns: 'usage'`
(which sacrifices SWC's performance benefits).
- In the Babel era, Next.js supported this via `@babel/preset-env`'s
`useBuiltIns` (PR #10574). That capability was lost when Next.js
migrated to SWC.

### How?

A new `experimental.swcEnvOptions` config is added. Its properties are
spread into the `env` block that Next.js passes to SWC for client-side
compilation, alongside the existing browserslist-derived `targets`.
Server-side compilation is unaffected (always targets `node`).

The option surface mirrors [SWC's preset-env
docs](https://swc.rs/docs/configuration/supported-browsers) 1:1, keeping
it familiar and forward-compatible.

```js
// next.config.js
module.exports = {
  experimental: {
    swcEnvOptions: {
      mode: 'usage',
      coreJs: '3.38',
    },
  },
}
```
#### Changes:
config-shared.ts — type definition with JSDoc
config-schema.ts — zod validation
next-swc-loader.ts → swc/options.ts — plumb config into the SWC env
block
Unit tests (6 cases) + e2e test (dev & production)

Related issues #66562, #63104, #74978
Related discussion #46724

---------

Co-authored-by: Benjamin Woodruff <benjamin.woodruff@vercel.com>
2026-04-03 20:27:04 -07:00
Niklas Mischkulnig 4d5840e3b7 Turbopack: experimental.immutableAssetToken (#88607)
This allows specifying a different deployment id for static immutable assets

- [x] rename `getImmutableAssetToken` to `getAssetToken` (because it can be non-immutable as well)
- [x] disable for webpack

Closes PACK-6534

For https://github.com/nextjs/adapter-vercel/pull/25

Part of PACK-6802
2026-02-27 11:55:47 +01:00
Niklas Mischkulnig 96618b8727 test: unflake lru-disk-eviction on Windows (#90465)
```
FAIL Turbopack test/unit/image-optimizer/lru-disk-eviction.test.ts
  LRU disk eviction
    √ should evict oldest entries on initialization (133 ms)
    √ should evict old entries when new entries are set (124 ms)
    × should promote entries on get() to prevent eviction (8 ms)
    √ should return the same LRU instance on subsequent calls (1 ms)
    √ should deduplicate concurrent init calls (2 ms)
    √ should handle empty cache directory (2 ms)
    √ should handle non-existent cache directory (2 ms)

  ● LRU disk eviction › should promote entries on get() to prevent eviction

    EPERM: operation not permitted, rmdir 'C:\Users\ADMINI~1\AppData\Local\Temp\next-lru-test-dOXRaF\y'
```
2026-02-25 01:34:20 -08:00
Niklas Mischkulnig c61d96e0bc next-image: fix dpl handling with unicode (#90420)
With skew protection enabled, image srcs were double-encoded:

1. The imported value might be `'/_next/static/media/äöüščří.55df2443.png?dpl=1234'`
2. Then `new URL(src, 'http://n')` would normalize the URL, leading to `/_next/static/media/%C3%A4%C3%B6%C3%BC%C5%A1%C4%8D%C5%99%C3%AD.55df2443.png?dpl=1234`
3. The the existing `?url=${encodeURIComponent(src)}` causes the already percent-encoded unicode characters to be encoded yet again.

It's unfortunate that we have to handroll some URL parsing here, maybe someone has a better idea


```
  ● Image Component Unicode Image URL › production mode › should load static unicode image

    expect(received).toMatch(expected)

    Expected pattern: /_next%2Fstatic%2Fmedia%2F%C3%A4%C3%B6%C3%BC%C5%A1%C4%8D%C5%99%C3%AD(.+)png/
    Received string:  "/_next/image?url=%2F_next%2Fstatic%2Fmedia%2F%25C3%25A4%25C3%25B6%25C3%25BC%25C5%25A1%25C4%258D%25C5%2599%25C3%25AD.55df2443.png&w=828&q=75&dpl=test-immutable-tkn-7890"

      21 |   it('should load static unicode image', async () => {
      22 |     const src = await browser.elementById('static').getAttribute('src')
    > 23 |     expect(src).toMatch(
         |                 ^
      24 |       /_next%2Fstatic%2Fmedia%2F%C3%A4%C3%B6%C3%BC%C5%A1%C4%8D%C5%99%C3%AD(.+)png/
      25 |     )
      26 |     const fullSrc = new URL(src, `http://localhost:${appPort}`)

      at Object.toMatch (integration/next-image-legacy/unicode/test/index.test.ts:23:17)



  ● Image Component Unicode Image URL › production mode › should load static unicode image

    expect(received).toMatch(expected)

    Expected pattern: /_next%2Fstatic%2Fmedia%2F%C3%A4%C3%B6%C3%BC%C5%A1%C4%8D%C5%99%C3%AD(.+)png/
    Received string:  "/_next/image?url=%2F_next%2Fstatic%2Fmedia%2F%25C3%25A4%25C3%25B6%25C3%25BC%25C5%25A1%25C4%258D%25C5%2599%25C3%25AD.55df2443.png&w=828&q=75&dpl=test-immutable-tkn-7890"

      22 |   it('should load static unicode image', async () => {
      23 |     const src = await browser.elementById('static').getAttribute('src')
    > 24 |     expect(src).toMatch(
         |                 ^
      25 |       /_next%2Fstatic%2Fmedia%2F%C3%A4%C3%B6%C3%BC%C5%A1%C4%8D%C5%99%C3%AD(.+)png/
      26 |     )
      27 |     const fullSrc = new URL(src, `http://localhost:${appPort}`)

      at Object.toMatch (integration/next-image-new/unicode/test/index.test.ts:24:17)

```
2026-02-24 17:28:51 +01:00
Niklas Mischkulnig 5c425fe2ce next-image: fix deployment id handling for unoptimized (#90416)
Previously

```jsx
import img from "./x.png";

// x is "/_next/static/media/test.abc123.png?dpl=foo"

<Image id="static-unoptimized" src={x} unoptimized />
```

would lead to `/_next/static/media/test.abc123.png?dpl=foo&dpl=foo`

#90420 adds some related tests for the legacy loader as well
2026-02-24 16:53:03 +01:00
Sebastian "Sebbie" Silbermann 212ced6fb0 Revert "keep next-env.d.s unchanged between dev and build" (#90384) 2026-02-23 23:32:06 +01:00
Steven 39eb8e0ac4 feat(next/image): add lru disk cache and images.maximumDiskCacheSize (#89963)
This PR adds an LRU disk cache so that reads and writes from the Image
Optimization API will evict old entries based on the value of
`images.maximumDiskCacheSize` configuration.

The LRU ensures that cache reads bump the entry to the top so that they
don't get evicted - only the least recently used entries get evicted.

When `next start` is run, if there is an existing disk cache the we will
replay the files in order to populate the LRU and respect
`images.maximumDiskCacheSize` if it was changed.

If no configuration is provided, default to 50% available disk space.
2026-02-20 20:12:26 +00:00
Jimmy Lai def0e3aabf fix: use Buffer.indexOf in uint8array helpers for faster byte scanning (3/8) (#89864)
## Summary

Use `Buffer.indexOf` when available in `uint8array-helpers.ts` for
faster byte sequence scanning. Falls back to the existing manual loop on
non-Node environments.

## Test plan

- [ ] `pnpm test-unit test/unit/stream-utils/uint8array-helpers.test.ts`
passes
2026-02-19 12:06:37 +01:00
Zack Tanner da445e89cd fix: normalize loopback only in hostname (#90158)
`NextURL` intentionally canonicalizes loopback hosts (127.x.x.x, [::1],
localhost) to localhost so that internal URL handling is consistent
across equivalent local origins.

However, we were applying a regex replacement to the entire URL before
parsing, which unintentionally rewrote loopback strings inside query
parameter values, eg `?redirect_uri=http%3A%2F%2F127.0.0.1...`. This is
unexpected for anyone that is explicitly passing through different
loopback values.

This fixes it by first parsing the URL with `new URL()`, and then only
apply the replacement logic to `parsed.hostname` when it is a loopback
host. This leaves search unchanged.

Fixes NEXT-4864
2026-02-18 11:54:24 -08:00
Daniel Tao 208c4f44b1 fix(image): findClosestQuality returns 0 for low quality values (#89621)
Fixes #89620

Fix `findClosestQuality` returning `0` when the `quality` prop on
`<Image>` is set to a low value (e.g. `quality={1}`), which causes the
image optimization server to reject the request with a 400 error.

### Cause

The `reduce` call in `findClosestQuality` used `0` as its initial
accumulator. Since `0` is not a valid quality value (must be 1-100), it
would win the "closest" comparison for any `quality <= 37` with the
default `qualities: [75]` config. This produced `q=0` in the image URL,
which the server rejects.

### Fix Description

Changed the `reduce` initializer from `0` to `config.qualities[0]`. This
is safe because the early return above already handles the empty array
case, so `config.qualities[0]` is always defined when reached.

Added a unit test for `quality=1` with `qualities=[75]` to verify it
returns `75` instead of `0`.

---------

Co-authored-by: Steven <steven@ceriously.com>
2026-02-09 10:11:38 -05:00
Sebastian "Sebbie" Silbermann b08e16b21a Revert "[prebuilt-skew-protection] feat: adding in automatic deploymentId" (#88942) 2026-01-23 12:22:37 +01:00
Brooke 7282ce6759 [prebuilt-skew-protection] feat: adding in automatic deploymentId (#88496)
Original [PR](https://github.com/vercel/next.js/pull/88012/) has a bug
that attempted to validate on environment variables. Edited function to
not run validations on environment variables, only on the user provided
deployment id.

<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:

## For Contributors

### Improving Documentation

- Run `pnpm prettier-fix` to fix formatting issues before opening the
PR.
- Read the Docs Contribution Guide to ensure your contribution follows
the docs guidelines:
https://nextjs.org/docs/community/contribution-guide

### Fixing a bug

- Related issues linked using `fixes #number`
- Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md

### Adding a feature

- Implements an existing feature request or RFC. Make sure the feature
request has been accepted for implementation before opening a PR. (A
discussion must be opened, see
https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added
(https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md


## For Maintainers

- Minimal description (aim for explaining to someone not on the team to
understand the PR)
- When linking to a Slack thread, you might want to share details of the
conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Closes NEXT-
Fixes #

-->

---------

Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2026-01-22 21:07:34 +00:00
Jiachi Liu 13b13ede48 [metadata] match the Metadata and ResolvedMetadata type (#88739) 2026-01-20 17:26:03 +01:00
Tobias Koppers 689f6fe38e Add NEXT_DEPLOYMENT_ID global (#86738)
- Adds a `process.env.NEXT_DEPLOYMENT_ID` -> `globalThis.NEXT_DEPLOYMENT_ID` replacement
- On startup, `globalThis.NEXT_DEPLOYMENT_ID` is initialized to `process.env.NEXT_DEPLOYMENT_ID_COMPILE_TIME`

This prepares for https://github.com/vercel/next.js/pull/88761 to replace `process.env.NEXT_DEPLOYMENT_ID_COMPILE_TIME` with reading `<html data-dpl-id>` so that we can remove the env var inlining

However, all of this is only done for Turbopack, because we cannot forward `globalThis.NEXT_DEPLOYMENT_ID` to web workers with Webpack easily.
2026-01-20 15:28:59 +01:00
Steven 54476c9c64 fetch(next/image): reduce maximumResponseBody from 300MB to 50MB (#88588)
Based on our metrics, the P99.9 for a source image is 47 MB so we can
adjust the default setting to be much lower (by changing from from 300
MB to 50 MB) in favor of reducing memory.
2026-01-15 14:34:13 -05:00
Jiachi Liu 0df959c1ab keep next-env.d.s unchanged between dev and build (#88428) 2026-01-14 13:51:22 +01:00
Brooke 201c9604b6 Revert "[prebuilt-skew-protection] feat: adding in automatic deploymentId" (#88449)
Reverts vercel/next.js#88012
2026-01-12 14:14:22 -08:00
Brooke 0daf2b58b4 [prebuilt-skew-protection] feat: adding in automatic deploymentId (#88012)
<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:

## For Contributors

### Improving Documentation

- Run `pnpm prettier-fix` to fix formatting issues before opening the
PR.
- Read the Docs Contribution Guide to ensure your contribution follows
the docs guidelines:
https://nextjs.org/docs/community/contribution-guide

### Fixing a bug

- Related issues linked using `fixes #number`
- Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md

### Adding a feature

- Implements an existing feature request or RFC. Make sure the feature
request has been accepted for implementation before opening a PR. (A
discussion must be opened, see
https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added
(https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md


## For Maintainers

- Minimal description (aim for explaining to someone not on the team to
understand the PR)
- When linking to a Slack thread, you might want to share details of the
conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

https://linear.app/vercel/issue/FLOW-5530/nextjs-support-automatic-deploymentid-generation

### Why?
So users can configure a deploymentId, to be used for skew protection
with prebuilts

### How?

Closes NEXT-
Fixes #

-->


https://linear.app/vercel/issue/FLOW-5530/nextjs-support-automatic-deploymentid-generation

So users can configure a deploymentId, to be used for skew protection
with prebuilts

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: JJ Kasper <jj@jjsweb.site>
Co-authored-by: Steven <steven@ceriously.com>
2026-01-12 13:27:26 -08:00
Steven 00e92280d2 feat(next/image)!: add images.maximumResponseBody config (#88183)
Add `images.maximumResponseBody` configuration to exit early when
attempting to optimize large source images.
2026-01-06 14:49:45 -08:00
Sebastian "Sebbie" Silbermann 35a566b383 [strict-route-types] Ensure cache profiles and routes are type-checked even if .next is excluded (#87768)
Flagged behind `experimental.strictRouteTypes`

This fixes issues where cache profiles where not included or routes not validated if `.next` was excluded to squeeze out every bit of TypeScript performance.

It also fixes a redundant type-check since we have separate dev and prod dist folders.  If you run `next dev` and (`next build` or `next typegen`), you'd type-check routes and links twice. Now `next-env.d.ts` only exists once where we decide which dist folder to type-check.

Going forward, new Next.js type-checking should be added to `next-env.d.ts`. That file is fully controlled by Next.js and churning it, does not churn users since it's not in version control.

`next-env.d.ts` will be moved into `.next` in a follow-up. Ideally independent of the dist folder i.e.  independent of `next dev` vs `next build`.

Closes https://linear.app/vercel/issue/NXT-124/

https://github.com/vercel/next.js/pull/87319 but flagged.
2026-01-06 16:21:25 +01:00
Zack Tanner 8a8a00d5d0 Revert "Move next-env.d.ts to dist dir" (#87311)
Reverts vercel/next.js#86752

This has consequences when `.next` is in `exclude`. Reverting for now.
2025-12-18 16:00:13 +00:00
Jiachi Liu 25da5f0426 Move next-env.d.ts to dist dir (#86752) 2025-12-18 10:43:45 +01:00
Steven dfc51d0f40 fix(next/image): handle ?dpl for src without protocol (#86836)
Follow up to previous PR based on this comment:
https://github.com/vercel/next.js/pull/86485#discussion_r2589758455

We want to avoid adding `?dpl` when the deployment is an absolute url
missing the protocol which is allowed when `unoptimized=true`.
2025-12-04 15:51:51 -05:00
Steven 12a80b453e fix(next/image): add ?dpl query string for local images (without static import) (#86485)
In a previous PR https://github.com/vercel/next.js/pull/73184 the `?dpl`
query string was added to local images from static imports but not to
images without the static import.

This was a subtle change based on malte's PR review comment here:
https://github.com/vercel/next.js/pull/73184#issuecomment-2499126751

However, I can't think of reason why we should differentiate these two
since they are both related to the current deployment (as opposed to
remote images which live outside the deployment).

Furthermore, this string matching is causing microfrontends to fail to
add the `?dpl` because they have a path prefix and thus fail skew
protection.

So this PR fixes both problems by loosening the string matching to look
for src images that start with `/`.
2025-12-04 16:35:02 +00:00
sam 7e093afb8c Eslint: Improve Google Tag manager third parties message (#51903)
Note: new PR description written by @timneutkens 

## What?

Improves the detection of Google Tag Manager vs Google Analytics.
As indicated by the author of this PR it was confusing that when you
have Google Tag Manager set up it gives you an error message saying
you're adding Google Analytics even though it's Tag Manager.

I've updated the PR to have a separate message for Google Tag Manager
and expanded the error message docs too.

It also mentioned `next/script` in a bunch of places even though the
error doc recommends `@next/third-parties`. I've updated all cases to
refer to `@next/third-parties` instead.

<details>
<summary>Previous PR description</summary>
Google has multiple products with similar names: the "Google Tag" and
"Google Tag Manager". Google Tag Manager uses `gtm.js` whereas the
"Google Tag" uses `gtag`. Google Analytics previously used
`analytics.js` (<2017) and is now delivered via the "Google Tag".

Google Tag: `www.googletagmanager.com/gtag/js`
Google Tag Manager: `www.googletagmanager.com/gtm.js`

The `next-script-for-ga.ts` rule's matching list
(`SUPPORTED_HTML_CONTENT_URLS`) includes `gtm.js` which means when a
developer adds Google Tag Manager to their NextJS website, they'll
receive the warning and be encouraged to swap from `gtm.js` to `gtag`. A
developer asleep at the wheel (...me) may think...

> I'm using Google Analytics via Google Tag Manager, and NextJS is
suggesting I swap `gtm.js` for `gtag` because it will improve
performance. `gtag` sounds like it is part of "Google Tag Manager" so I
am going to follow the instructions so that my website is faster.

However, `gtag` is **not** compatible with `gtm.js` (they're different
products with similar names and similar purposes) and so making this
change will cause very confusing Google Tag Manager behaviour. I am
suggesting that `gtm.js` is removed completely from the rule, because
anyone using `gtm.js` should not be following these instructions. A
separate rule could be added specifically for Google Tag Manager, that
does not refer to `gtag`.
</details>

---------

Co-authored-by: Tim Neutkens <tim@timneutkens.nl>
2025-11-24 14:29:52 +01:00