Building on top of @agadzik work (see first commit)
Adds prerender route metadata to help downstream consumers understand
the nature of build-time prerenders
We break down prerender taxonomy as follows
routeType describes what the prerender serves:
* `route`: a non-UI endpoint, such as a Route Handler.
* `page`: a page whose route parameters are fully known.
* `shell`: a reusable PPR shell for a parameterized page.
* `fallback`: a fallback that may serve or specialize additional
parameter values.
response describes how complete the static response is:
* `complete`: the prerender contains the completed response.
* `initial`: an initial response is available, but the page UI is
incomplete; in practice this applies to partially prerenderable UI
routes.
* `empty`: there is no initial static response to send.
compute describes the request-time work required:
* `static`: no request-time server compute is needed.
* `resuming`: request-time compute resumes from the static response.
* `blocking`: the initial response is blocked until request-time compute
starts, but it may stream while that compute continues.
`htmlSize` reports the byte size of prerendered HTML when the output has
HTML.
The taxonomy is included only on a prerender group’s canonical primary
output—the HTML output for pages or the response output for Route
Handlers—and is omitted from secondary RSC and segment-data outputs.
---------
Co-authored-by: agadzik <andrew.gadzik@vercel.com>
### What?
Migrate remaining direct `next-webdriver` test callers that have a
`NextInstance` to `next.browser()`, and expose the shared `Playwright`
browser type from `e2e-utils`.
### Why?
`NextInstance.browser` should be the supported browser-opening interface
for test fixtures, with `next-webdriver` kept as the private
implementation detail.
### How?
Updated affected development, e2e, and production tests to call
`next.browser()` directly, passing `baseUrl` where tests intentionally
target a manually spawned or proxied server. Shared helpers now receive
browser callbacks from the test context, and browser types import
`Playwright` from `e2e-utils` instead of deriving from `next.browser` or
importing from private paths.
<!-- NEXT_JS_LLM_PR -->
## What?
Converts existing `createNext()` usage into `nextTestSetup()`.
`createNext()` was the setup step we had before `nextTestSetup()` was
added.
This PR focused on the simple conversion cases. There will be a
follow-up to complete the last few.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
When a route with params specifies `generateStaticParams`, the meaningful outputs are the sub-paths contained within. The top level route just corresponds with the file path, which isn't representative of a static or partially prerendered output. Previously it'd just inherit one of the sub-path values (ie if one path was PPRed, it'd show PPR, even if some were fully static).
This removes the symbol from those types of outputs, since #92518 moved those symbols to be within the subpaths.
For truly dynamic param cases (ie, no subpaths), the rendering symbol will still appear.
## What
Removes the `Buffer.from()` wrapper when constructing `RenderResult` for
`/_next/data/` JSON responses in the Pages Router handler.
## Why
PR #80189 introduced
`Buffer.from(JSON.stringify(result.value.pageData))`
when building the data response. Since `RenderResult.isDynamic` checks
`typeof this.response !== 'string'`, passing a `Buffer` (not a `string`)
caused it to return `true`, making `sendRenderResult` treat the response
as a dynamic stream — skipping `Content-Length` and `ETag` generation
and
falling back to `Transfer-Encoding: chunked`.
This is a regression from v15.4.0 and breaks CDN-side compression for
self-hosted deployments (e.g. CloudFront requires `Content-Length` to
compress origin responses on-the-fly).
## Fix
```diff
- Buffer.from(JSON.stringify(result.value.pageData)),
+ JSON.stringify(result.value.pageData),
```
## Testing
- Reproduction steps verified against the reporter's repro repo:
https://github.com/bbrouse/nextjs-content-length-repro
- ```diffcurl -sD - on /_next/data/<BUILD_ID>/index.json``` now returns
Content-Length and ETag headers.
## Affected Area
- Pages Router — /_next/data/ responses only
- No impact on App Router
- Single-line change, minimal blast radius
Fixes#90281
---------
Co-authored-by: JJ Kasper <jj@jjsweb.site>
### What?
Removes the `experimental.devCacheControlNoCache` config option entirely
and hard-codes `no-cache, must-revalidate` as the dev server
`Cache-Control` header value.
Previously the option controlled whether the dev server responded with:
- `no-store, must-revalidate` (default, `false`)
- `no-cache, must-revalidate` (opt-in, `true`)
This PR first flips the default to `true`, then removes the option
altogether — making `no-cache, must-revalidate` unconditional in all dev
code paths.
### Why?
`no-cache` is strictly better than `no-store` for the dev server:
- `no-cache` allows the browser to revalidate (conditional
`If-None-Match`/`If-Modified-Since` requests), letting the server
respond with `304 Not Modified` when nothing changed → faster page loads
during development.
- `no-store` forces a full re-fetch every time, discarding valid cached
responses.
Since `no-cache` is the correct behavior for all dev users, the toggle
has no remaining value and can be removed to simplify the codebase.
### How?
**Two commits:**
1. `bcec825` — Flip the default from `false` → `true`; update
tests/fixtures/manifest to reflect the new default.
2. `e6e919f` — Remove the option entirely:
- Deleted `devCacheControlNoCache?: boolean` from `ExperimentalConfig`
interface, `config-schema.ts` Zod schema, `defaultConfig`,
`NextConfigRuntime`, and `getNextConfigRuntime()`.
- Replaced all four conditional ternaries in `base-server.ts`,
`router-server.ts`, `pages-handler.ts`, and `app-page.ts` with the
hard-coded string `'no-cache, must-revalidate'`.
- Deleted the `dev-cache-control-no-cache-disabled` test suite (was
testing the `false` path which no longer exists).
- Simplified the remaining `dev-cache-control-no-cache` test (removed
experimental framing).
- Synced `rspack-dev-tests-manifest.json`.
---------
Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
When we landed #76207 we started propagating the
`stale-while-revalidate` value to RSC responses. We wanted a way to
express to CDNs that it can keep serving the cached content for some
time while a revalidation runs in the background, but we specifically
don't want that for private caches.
As a result of this header, when re-building your application locally
(or doing something on the server that would have expired the cache),
the browser's disk cache would serve a stale RSC response. We currently
encode a buildId in RSC payloads, so the client router will discard RSC
responses that don't match what it is expecting.
Unfortunately there isn't a `s-stale-while-revalidate` header to express
the intended semantics. This moves the `stale-while-revalidate`
configuration into the `CDN-Cache-Control` header, which is becoming
standardized and is supported by a wide variety of CDNs. This allows us
to specify the SWR behavior more explicitly to CDNs while not impacting
private caches like the browser. It restores the previous behavior of
only setting `s-maxage` on the `cache-control` itself to avoid
influencing browser cache.
If Next.js is deployed to a CDN that doesn't support this header, it can
be customized via `nextConfig.experimental.cdnCacheControlHeader`. Eg,
if deployed to Fastly, you'd configure this to `cdnCacheControlHeader:
'Surrogate-Control'`.
Closes NAR-525
Reference docs:
[Vercel](https://vercel.com/docs/headers/cache-control-headers#cdn-cache-control-header)
[Cloudflare](https://developers.cloudflare.com/cache/concepts/cdn-cache-control/)
[Fastly](https://www.fastly.com/documentation/reference/http/http-headers/Surrogate-Control/)
[RFC 9213](https://httpwg.org/specs/rfc9213.html)
This test has started failing when deployed to hosted environments, but
it consistently is passing in `next start` tests.
We could retry the on-demand revalidation and the test will pass, but we
lose the value in the test.
This instead temporarily disables it until it can be investigated
upstream.
[slack
x-ref](https://vercel.slack.com/archives/C04KC8A53T7/p1757108995256419)
This ensures we don't treat cache hits for lazily generated `fallback:
true` routes as the fallback itself which has a different cache-control
header than cache hits should.
Fixes: https://github.com/vercel/next.js/issues/80838
For the `stale-while-revalidate` value in the `cache-control` response header, Next.js currently uses the configured
[`expireTime`](https://nextjs.org/docs/app/api-reference/config/next-config-js/expireTime), which applies to all routes, and defaults to one year.
With the introduction of `"use cache"` and granular [cache lifetimes](https://nextjs.org/docs/app/api-reference/functions/cacheLife), users can now set expire times per route — based on the minimum expire time of all cached functions used by that route.
This PR updates the `stale-while-revalidate` value to reflect the route's cache lifetime, using the difference between the minimum expire time and the minimum revalidate time. If no explicit expire time is defined in cache profiles, the globally configured `expireTime` is used.
Additionally, the collected expire time is added to the prerender manifest as `initialExpireSeconds` (and `fallbackExpire`), analogous to `initialRevalidateSeconds` (or `fallbackRevalidate`).
closes NAR-100
This PR is a follow-up to #76100 that enables on-demand revalidation via
an API route in development mode. This is accomplished by implementing
three changes:
1. We need to forward the user cookies, including the
`__next_hmr_refresh_hash__` value, which is required to generate the
same cache key _during_ the revalidation as _before_ and, more
importantly, _after_ the revalidation. Otherwise the revalidated cache
entries will use a different cache key (without the refresh hash), and
on subsequent page reload, the stale cached data would still be shown
(using the cache key with refresh hash).
2. When checking if a `revalidate()` call succeeded, we allow `200`
responses in general, and only check the `'x-nextjs-cache'` header for
`404` response. This matches the historic intent of the feature.
([x-ref-1](https://github.com/vercel/next.js/pull/36108/files#diff-8d464ed8b3d6ed08deabeaa05180900c38f8943d75368798861d1734103512fcR338-R342),
[x-ref-2](https://github.com/vercel/next.js/pull/34826/files#diff-8d464ed8b3d6ed08deabeaa05180900c38f8943d75368798861d1734103512fcR318-R324)).
3. The `isOnDemandRevalidate` status is now also set for non-`isSSG`
pages, based on the `'x-prerender-revalidate'` header. This matches the
logic in the incremental cache handler, which enables cache revalidation
in dev mode when using `unstable_cache`.
## Problem
When generating route regular expressions for data routes, some code
paths appear to not escape the `.` in `.json`. This causes routing to be
slightly slower, prevents some optimizations related to static
postfixes, and potentially allows for unintended request paths to be
accepted as value.
## Solution
After much tracing and digging, I found a code path writing regular
expressions directly without escaping. In this case the solution is to
escape the `.` using `\\.` when generating static data routes.
A simple next app was made to exercise this path with a route
`pages/server-time.tsx` that contains a simple `getServerSideProps`.
```tsx
import type { InferGetServerSidePropsType, GetServerSideProps } from 'next'
type TimeDate = {
timestamp: number
}
export const getServerSideProps = (async () => {
return { props: { timestamp: Date.now() } }
}) satisfies GetServerSideProps<TimeDate>
export default function Page({
timestamp,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
return (
<main>
<p>{new Date(timestamp).toUTCString()}</p>
</main>
)
}
```
Before the change, the generated route regexp was
`"^/_next/data/[build_id]/server-time.json$"`, but after then change it
becomes `"^/_next/data/[build_id]/server-time\\.json$"`
The related tests were updated to expect properly escaped `\\.json$`
endings to the regexps.
- Change the Next manifest output assets to also track references to output assets there. That way, `page.js.nft.json` lists the referenced output assets of `page.js` and e.g. `page_client-reference-manifest.js` and all the chunks references in the manifest as well.
- For `ResolveResultItem::External`, continue resolving with a fresh `ModuleAssetContext` that doesn't have any of the build-time settings (because the Node environment when running the server won't have these settings either).
Closes PACK-3380
The host header is necessary for determining the current domain locale
for ISR so we need to ensure it's in the default allowed headers list.
Without this header we aren't able to render links accurately during
revalidations.
x-ref: https://github.com/vercel/next.js/issues/71848
x-ref: NEXT-3842
We will wait the server to respond with a compile success message after
a file is patched before proceeding to the next steps in the test to
reduce flakiness.
By doing so, we uncovered a few tests that were passing accidentally due
to flakiness of `patchFile`, and fixed them in the PR.
Pages router (`/pages`) will continue to support React 18 not the React
19 RC. Current thinking is that we'll add support for React 19 in Pages
Router once 19 is stable.
This does not affect App Router (`/app`) which continues to use the
latest React Canary (i.e. React 19).
https://github.com/vercel/next.js/pull/65058 is required reading to
understand the changes in this PR
---------
Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com>
This particular test was failing because build logs were being piped to
stderr, despite being successful. This ensures `next.cliOutput` captures
both `stdout` and `stderr`.
...with `assertHasRedbox` and `assertNoRedbox`.
`hasRedbox()` has a hardcoded timeout of 5s that is only required for
the negative assertion.
Instead, we now have dedicated assertions for the positive
(`assertHasRedbox`) and negative case (`assertNoRedbox`).
The negative assertion still has the hardcoded timeout.
But the positive assertion just retries until we find the Redbox.
This speeds up tests using the positive assertion.
Removing `hasRedbox` also uncovered some unused expressions e.g. `await
hasRedbox(browser)`.
These expressions probably wanted to use `expect(await
hasRedbox(browser)).toBe(true)
This ensures our dynamic routes that have the same specificity as
`_next/static/:path*` don't get matched unexpectedly when the
`_next/static` asset doesn't exist. We were holding off on making this
change explicit due to compatibility concerns but these are no longer a
concern and the unexpected matching is more of a concern.
Closes: CSM-11
Fixes: https://github.com/vercel/next.js/issues/19270
Closes NEXT-2613
## What?
In Next, rendering a route involves 3 layers:
- the routing layer, which will direct the request to the correct route to render
- the rendering layer, which will take a route and render it appropriately
- the user layer, which contains the user code
In #51831, in order to optimise the boot time of Next.js, I introduced a change that allowed the routing layer to be bundled. In this PR, I'm doing the same for the rendering layer. This is building up on @wyattjoh's work that initially split the routing and the rendering layer into separate entry-points.
The benefits of having this approach is that this allows us to compartmentalise the different part of Next, optimise them individually and making sure that serving a request is as efficient as possible, e.g. rendering a `pages` route should not need code from the `app router` to be used.
There are now 4 different rendering runtimes, depending on the route type:
- app pages: for App Router pages
- app routes: for App Router route handlers
- pages: for legacy pages
- pages api: for legacy API routes
This change should be transparent to the end user, beside faster cold boots.
## Notable changes
Doing this change required a lot of changes for Next.js under the hood in order to make the different layers play well together.
### New conventions for externals/shared modules
The big issue of bundling the rendering runtimes is that the user code needs to be able to reference an instance of a module/value created in Next during the render. This is the case when the user wants to access the router context during SSR via `next/link` for example; when you call `useContext(value)` the value needs to be the exact same reference to one as the one created by `createContext` earlier.
Previously, we were handling this case by making all files from Next that were affected by this `externals`, meaning that we were marking them not to be bundled.
**Why not keep it this way?**
The goal of this PR as stated previously was to make the rendering process as efficient as possible, so I really wanted to avoid extraneous fs reads to unoptimised code.
In order to "fix" it, I introduced two new conventions to the codebase:
- all files that explicitly need to be shared between a rendering runtime and the user code must be suffixed by `.shared-runtime` and exposed via adding a reference in the relevant `externals` file. At compilation time, a reference to a file ending with this will get re-written to the appropriate runtime.
- all files that need to be truly externals need to be suffixed by `.external`. At compilation time, a reference to it will stay as-is. This special case is needed mostly only for the async local storages that need to be shared with all three layers of Next.
As a side effect, we should be bundling more of the Next code in the user bundles, so it should be slightly more efficient.
### App route handlers are compiled on their own layer
App route handlers should be compiled in their own layer, this allows us to separate more cleanly the compilation logic here (we don't need to run the RSC logic for example).
### New rendering bundles
We now generate a prod and a dev bundle for:
- the routing server
- the app/pages SSR rendering process
- the API routes process
The development bundle is needed because:
- there is code in Next that relies on NODE_ENV
- because we opt out of the logic referencing the correct rendering runtime in dev for a `shared-runtime` file. This is because we don't need to and that Turbopack does not support rewriting an external to something that looks like this `require('foo').bar.baz` yet. We will need to fix that when Turbopack build ships.
### New development pipeline
Bundling Next is now required when developing on the repo so I extended the taskfile setup to account for that. The webpack config for Next itself lives in `webpack.config.js` and contains the logic for all the new bundles generated.
### Misc changes
There are some misc reshuffling in the code to better use the tree shaking abilities that we can now use.
fixes NEXT-1573
Co-authored-by: Alex Kirszenberg <1621758+alexkirsz@users.noreply.github.com>
This adds new `build and test` and `build and deploy` workflows in favor
of the existing massive `build, test, and deploy` workflow. Since the
new workflows will use `pull_request_target` this waits to remove the
existing workflow until the new one is tested.
While testing this new workflow flakey behavior in tests have also been
addressed. Along with the new workflow we will also be leveraging new
runners which allow us to run tests against the production binary of
`next-swc` so this avoids slight differences in tests we've seen due to
running against the dev binary.
Furthermore we will have a new flow for allowing workflow runs on PRs
from external forks which will either require a comment be checking a
box approving the run after each change or a label added by the team.
The new flow also no longer relies on `actions/cache` or similar which
have proven to be pretty unreliable.
Tests runs with the new workflow can be seen here
https://github.com/vercel/next.js/actions/runs/5100673508/jobs/9169416949
With the addition of the query prefix we can hit the max length for PCRE
named matches so this reduces the prefix length and ensures we go
through the param name validation still
x-ref: https://twitter.com/simonecervini/status/1644123851003928579
This ensures we prefix the dynamic route params in the query so that
they can be kept separate from actual query params from the initial
request.
Fixes: https://github.com/vercel/next.js/issues/43139
This ensures we properly honor the `export const fetchCache` config and
also ensures we properly bypass fetch-cache when an On-Demand
Revalidation is occurring.
The `export const dynamic` handling is not changed here as that was
behaving correctly and should not influence fetch cache handling only
whether a page is prerendered fully or treated as SSR.
Fixes: https://github.com/vercel/next.js/issues/47273
x-ref: [slack
thread](https://vercel.slack.com/archives/C042LHPJ1NX/p1679078572123979)