### What?
Switch Turbopack's hash encoding for chunk and asset output filenames
from hexadecimal (base16) to base40, using the alphabet \`0-9 a-z _ - ~
.\`. Version hashes (used for HMR update comparison, not filenames) use
base64 instead.
### Why?
Base40 encodes the same number of bits in fewer characters than hex,
producing shorter output filenames. All 40 characters are RFC 3986
unreserved (URL-safe) and safe on case-insensitive filesystems (macOS
HFS+/APFS, Windows NTFS).
Hash truncation lengths are reduced proportionally to maintain
equivalent collision resistance:
| Context | Before (hex) | After (base40) | Entropy |
|---|---|---|---|
| Content hash in chunk filenames | 16 chars | 13 chars | ~69 bits |
| Content hash in asset filenames | 8 chars | 13 chars | ~69 bits |
| Ident disambiguator hash | 8 chars | 7 chars | ~37 bits |
| Long-path prefix hash | 5 chars | 4 chars | ~21 bits |
### How?
**New encoding module** (\`turbo-tasks-hash/src/base40.rs\`):
- Defines the base40 alphabet and length constants (\`BASE40_LEN_64 =
13\`, \`BASE40_LEN_128 = 25\`)
- Implements a generic \`encode_base40_fixed<N>\` helper to avoid
duplication
- Public API: \`encode_base40(u64) -> String\` and
\`encode_base40_128(u128) -> String\`
**New base64 encoding** (\`turbo-tasks-hash/src/base64.rs\`):
- \`encode_base64(u64) -> String\` — 11-char base64 (no padding) for
version hashes
- Version hashes don't appear in URLs or filenames, so base64 is safe
and shorter
**New \`HashAlgorithm\` variants** (\`turbo-tasks-hash/src/lib.rs\`):
- \`Xxh3Hash64Base40\` and \`Xxh3Hash128Base40\` added alongside
existing hex variants
- Existing hex variants kept for internal manifests and identifiers
**\`ContentHashing\` moved to \`turbopack-core\`**:
- Moved from \`turbopack-browser\` to
\`turbopack-core/src/chunk/mod.rs\` so both \`BrowserChunkingContext\`
and \`NodeJsChunkingContext\` can use it
**Separate chunk vs asset content hashing**:
- \`BrowserChunkingContext\`: \`content_hashing\` renamed to
\`chunk_content_hashing\` (optional), new \`asset_content_hashing:
ContentHashing\` field (non-optional, defaults to 13 chars)
- \`NodeJsChunkingContext\`: new \`asset_content_hashing:
ContentHashing\` field (non-optional, defaults to 13 chars)
- Builder methods: \`use_content_hashing()\` renamed to
\`chunk_content_hashing()\`, new \`asset_content_hashing()\`
**Version hashes switched to base64**:
- \`turbopack-nodejs/src/ecmascript/node/version.rs\`
- \`turbopack-dev-server/src/html.rs\`
- \`turbopack-browser/src/ecmascript/version.rs\`,
\`merged/version.rs\`, \`list/version.rs\`
**Other callers updated** (15 files across turbopack and next-core):
- All chunk/asset content hashing switched from \`Xxh3Hash128Hex\` →
\`Xxh3Hash128Base40\`
- \`ContentHashing::Direct { length }\` reduced from 16 → 13
- Asset path truncations use full 13-char base40 hash (matching chunk
filenames)
**Exception — \`wasm_edge_var_name\`** (\`turbopack-wasm/src/lib.rs\`):
- Kept as \`Xxh3Hash128Hex\` because the hash is used as part of a
JavaScript variable name (\`wasm_{hash}\`), and base40 characters \`-\`,
\`~\`, \`.\` are not valid JS identifier characters.
**Scope — NOT changed:**
- Webpack configuration (unchanged)
- Internal manifests (\`routes_hashes_manifest\`,
\`project_asset_hashes_manifest\`)
- Internal identifiers (font naming, external module hashing, data URI
sources, debug IDs)
- SRI hashes (SHA-based Base64, different purpose)
---------
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Enable skew protection for all start-mode tests when Turbopack is enabled (i.e. not dev). This works by setting `NEXT_DEPLOYMENT_ID` in the test infra
- When running the tests, `packages/next/src/server/lib/router-utils/resolve-routes.ts` now validates that all static asset requests have the correct `?dpl=...` query param value, and returns a 404 if missing.
- It can be disabled with `disableAutoSkewProtection: true` in the test options
- There were various tests that were asserting URLs and were missing the optional `?dpl` at the end.
- `next.getDeploymentIdQuery()` can be used to get the `?dpl=dpl_123912js` string (or an empty string)
- `next.getAssetQuery()` currently behaves like `getDeploymentIdQuery`, but will switch to prefer the immutable static token in #88607
*This doesn't enable skew protection for any new tests, only preparation*
Prepare a lot of test assertions so that they work correctly when a `?dpl=123` query param is present:
- some regexes should accept the query
- some places assumed that the `<script>`'s `src` is a valid pathname, whereas it might have a query param that has to be stripped before removing the path
- some hardcoded build ids instead of `next.buildId`
- `expect(hasImageMatchingUrl(browser, "...")).toBe(true)` has very bad error messages if the assertion fails
- there was a `(process.env.TURBOPACK_DEV ? describe.skip : describe)('production mode',` around a dev test
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
Absolutely no idea how this was passing in the integration test setup
For Webpack, setting it via the config was previously completely ignored and only the `--profile` flag was respected
Turbopack was already working correctly: https://github.com/vercel/next.js/pull/84920
We want `FileContent.content_hash()` to only hash the content bytes. This is also needed to produce the correct hashes [for SRI](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Subresource_Integrity)
However, the default `DeterministicHash` and `Hash` impls are ill-suited for that because they're a generic hashing infrastructure.
Hashing only the content bytes isn't what you want actually, otherwise `hash(("a", "b", "c"))` would be the same as `hash("abc")`
https://doc.rust-lang.org/std/hash/trait.Hasher.html#method.write_str describes this problem as well
This is exactly what we want for Rope though, so this adds a `Rope.content_hash() -> impl DeterministicHash`.
Additionally, this changes `content_hash` to accept a HashAlgorithm to prepare for sha SRI hashes. This required changing the return type to `RcStr`, because of the variable hash lengths. But the return value was always `encode_hex`d everywhere anyway.
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.
This should reduce some duplicate CI jobs.
This split existed to support separate dev/prod manifests for skipping tests. That's not needed anymore, so we can just switch to the Webpack setup. This manual partitioning of `integration` tests into dev vs prod was brittle anyway
We still need this setup for rspack though
This was flakey with Turbopack:
```
Error: TIMED OUT: /New page/
404
This page could not be found.
undefined
at check (/root/actions-runner/_work/next.js/next.js/test/lib/next-test-utils.ts:726:9)
at Object.<anonymous> (/root/actions-runner/_work/next.js/next.js/test/integration/read-only-source-hmr/test/index.test.ts:124:7)
```
But somehow Webpack doesn't automatically refresh the page here?
https://github.com/user-attachments/assets/b363dbff-60d1-4813-bb1c-e5a64f0312d3
For some locations LightningCSS is 1-indexed (but not all of them, the rustdoc describes it though)
IssueSource is always 0-indexed
Previously, it looked like this
```
Error: Turbopack build failed with 1 errors:
./bench/basic-app/app/[variants]/styles.css:7:12
Module not found: Can't resolve './test1.png'
5 | .style {
6 | cursor: url(./test1.png);
> 7 | }
| ^
8 |
```
AI coding agents often rely on outdated training data when working with
Next.js projects, leading to hallucinated APIs and deprecated patterns.
This PR addresses that by shipping version-matched documentation inside
the `next` npm package and generating agent instruction files during
`create-next-app`.
During `next` build, a new `copy_docs` task copies `docs/` from the repo
root into `dist/docs/`. Since `"dist"` is already in the package's
`files` array, the docs are automatically published at
`node_modules/next/dist/docs/`.
`create-next-app` gains a new `--agents-md` / `--no-agents-md` flag
(defaults to `true`). When enabled it writes two files:
- `AGENTS.md` — instructs agents to read `node_modules/next/dist/docs/`
instead of relying on training data
- `CLAUDE.md` — uses `@AGENTS.md` import syntax for Claude Code
The option appears in the recommended defaults display and is prompted
during the customize flow. Existing tests are updated with
`--no-agents-md` to avoid the new prompt.
## What?
Remove the `--turbopack` and `--webpack` CLI flags from
`create-next-app`.
## Why?
Turbopack is now the default bundler in Next.js. The `--turbopack` flag
is redundant since it's already the default, and the `--webpack` flag is
no longer needed as a `create-next-app` option. The only alternative
bundler flag retained is `--rspack`.
## How?
- Removed `--turbopack` and `--webpack` option definitions from the CLI
in `packages/create-next-app/index.ts`
- Simplified bundler selection logic: defaults to Turbopack, with
`--rspack` as the only override
- Removed `--turbopack` documentation from
`packages/create-next-app/README.md`
- Removed all `--no-turbopack` flags from test invocations across all
create-next-app test files
- Removed `--webpack` test cases from app, pages, and app-api template
tests
When proxying through multiple projects, this header would get overwritten by the proxy project:
- the header would be the proxy's deployment id
- `process.env.NEXT_DEPLOYMENT_ID` would be the origin's deployment id
Needs https://github.com/vercel/vercel/pull/14903
Previously:
```
pnpm next build
▲ Next.js 16.2.0-canary.28 (Turbopack)
Creating an optimized production build ...
✓ Compiled successfully in 5.7s
Running TypeScript ...
Failed to compile.
./app/page.tsx:3:5
Type error: Type 'string' is not assignable to type 'number'.
1 | import Nav from "../components/Nav";
2 |
> 3 | let x: number = 'abc'
| ^
4 |
5 |
6 | const IndexPage = () => (
Next.js build worker exited with code: 1 and signal: null
```
Now:
```
pnpm next build
▲ Next.js 16.2.0-canary.28 (Turbopack)
Creating an optimized production build ...
✓ Compiled successfully in 5.7s
Running TypeScript ...
Failed to type check.
./app/page.tsx:3:5
Type error: Type 'string' is not assignable to type 'number'.
1 | import Nav from "../components/Nav";
2 |
> 3 | let x: number = 'abc'
| ^
4 |
5 |
6 | const IndexPage = () => (
Next.js build worker exited with code: 1 and signal: null
```
Reapply https://github.com/vercel/next.js/pull/88959
Revert https://github.com/vercel/next.js/pull/89323
Closes PACK-6537
Closes PACK-6756
- Deployment skew (forcing a MPA nav instead of a SPA nav if a deployment happened inbetween) is handled by comparing a header on the client side.
- The other case that used build ids was app router, which was changed to the same approach in https://github.com/vercel/next.js/pull/88855
- Then, when a deployment id is available, make the build id a constant
- Particularly, this makes the rollout of this much easier as the data route paths stay the same (both in Next.js itself as well as in the builder)
If you use `output:export` and set `config.deploymentId` (i.e. enable skew protection) you are still expected to set these headers.
## What?
Use signal-based exit codes when shutting down the Next.js server to
prevent the process from waiting for the debugger to disconnect.
## Why?
When Node.js devtools are attached (via `--inspect` flag or the Next.js
DevTools "Attach debugger" feature), pressing Ctrl+C causes the dev
server to hang instead of exiting immediately. This happens because
calling `process.exit(0)` makes Node.js treat it as a normal exit, which
waits for all debuggers to disconnect before terminating.
In contrast, plain Node.js terminates immediately on SIGINT even with a
debugger attached, because signal-based termination doesn't wait for
debuggers.
## How?
Instead of calling `process.exit(0)` after cleanup, use signal-based
exit codes:
- SIGINT (signal 2) → `process.exit(130)` (128 + 2)
- SIGTERM (signal 15) → `process.exit(143)` (128 + 15)
This tells Node.js the process was terminated by a signal, which avoids
the debugger wait behavior.
Changes:
- Modified the cleanup function to accept the signal that triggered it
- Use `process.exit(signal === 'SIGINT' ? 130 : 143)` instead of
`process.exit(0)`
- Removed the previous `inspector.close()` workaround as it's no longer
needed
---------
Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>
Closes PACK-6537
When a deployment id is available, don't put the data routes at `_next/data/<BUILDID>/page.json`, but at `_next/data/page.json`.
Deployment skew (forcing a MPA nav instead of a SPA nav if a deployment happened inbetween) is handled by comparing a header on the client side.
If you use `output:export` and set `config.deploymentId` (i.e. enable skew protection) you are still expected to set these headers.
---
- [x] Find the right place where the header should be added for prerendered static RSC responses (route manifest)
Needed for https://github.com/vercel/vercel/pull/14795
Keep the file so that we can instead dynamically render it as opposed to having to bundle the prerender into the function
- if you `await __turbopack_load_by_url__ `, it waits for the exact chunk url you passed in to load
- but when that chunk actually loads, it registers itself with `stripQuery(chunkurl)+CHUNK_SUFFIX`
so there was mismatch if you load a chunk from a different deployment and thus `await __turbopack_load_by_url__` never resolves
----
Very confused about what's going on here:
```
FAIL Turbopack test/e2e/app-dir/segment-cache/deployment-skew/deployment-skew.test.ts (61.629 s)
segment cache (deployment skew)
with BUILD_ID
✓ does not crash when prefetching a dynamic, non-PPR page on a different deployment (11673 ms)
✓ does not crash when prefetching a static page on a different deployment (10744 ms)
with NEXT_DEPLOYMENT_ID
✓ does not crash when prefetching a dynamic, non-PPR page on a different deployment (10924 ms)
✕ does not crash when prefetching a static page on a different deployment (15756 ms)
● segment cache (deployment skew) › with NEXT_DEPLOYMENT_ID › does not crash when prefetching a static page on a different deployment
page.waitForSelector: Timeout 5000ms exceeded.
Call log:
- waiting for locator('#build-id') to be visible
519 |
520 | return this.startChain(async () => {
> 521 | const el = await page.waitForSelector(selector, {
| ^
522 | timeout,
523 | state,
524 | })
at waitForSelector (lib/browsers/playwright.ts:521:29)
at Playwright._chain (lib/browsers/playwright.ts:651:23)
at Playwright._chain [as startChain] (lib/browsers/playwright.ts:632:17)
at Playwright.startChain [as waitForElementByCss] (lib/browsers/playwright.ts:520:17)
at Playwright.waitForElementByCss [as elementByCss] (lib/browsers/playwright.ts:405:17)
at Playwright.elementByCss [as elementById] (lib/browsers/playwright.ts:425:17)
at Object.elementById (e2e/app-dir/segment-cache/deployment-skew/deployment-skew.test.ts:122:37)
```
In the browser debugger, this line here in `fetchServerResponse` never returns, so there is actually no navigation at all (neither SPA nor MPA) when clicking the link, should be doing an MPA nav
https://github.com/vercel/next.js/blob/d4e64f52bb9aeb5dbcf6bb484f204742b2e3d743/packages/next/src/client/components/router-reducer/fetch-server-response.ts#L242
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>
### What?
Fixed TypeScript error where `RedirectType.replace` and
`RedirectType.push` were typed as `string` instead of literal types,
causing compilation errors when used with `redirect()`.
### Why?
`RedirectType` constant properties weren't properly typed as literal
types, causing TypeScript to see them as generic `string` values instead
of the expected `'push' | 'replace'` union.
### How?
- Added `as const` to `RedirectType` properties in
`packages/next/src/client/components/navigation.react-server.ts`.
Fixes#88627
---------
Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com>
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.
When self-hosting Next.js, it's possible to customize a cache handler
for ISR. Self-hosted Next.js also makes use of a similar cache interface
for `next/image`, but there's no way to customize this cache handler.
This plugs into the [existing
ability](https://nextjs.org/docs/app/api-reference/config/next-config-js/incrementalCacheHandlerPath)
to customize a `cacheHandler` to support the `IMAGE` type. This lets you
implement whatever constraints you might have on your self-hosted
environment, eg the need for an LRU cache, or storing optimized images
outside of disk.
This is currently opt-in because it's a breaking change for cache
handlers to start receiving `IMAGE` cache types, which we'll make
default in the next major.
## Move "Ready in X" log before config loading and eliminate duplicate
`loadConfig` calls
### What?
Reorder the dev server startup flow so the "Ready in X" log appears
before loading the user's `next.config.js`, and eliminate duplicate
`loadConfig` calls in both dev and build paths.
### Why?
Previously, the "Ready in X" time included the time spent loading and
evaluating the user's `next.config.js` file. This made the framework
appear slower than it actually is, especially for projects with complex
config files or slow config evaluation.
Additionally, config was being loaded twice:
- **Dev server**: Once in `getStartServerInfo()` just for logging, then
again in `getRequestHandlers()`
- **Build**: Once for the main build, then again in
`getStartServerInfo()` just for logging
### How?
**Before:**
```
1. getStartServerInfo() → loadConfig #1 (slow - blocks on next.config.js)
2. logStartInfo() → logs version, URLs, experimental features
3. "Ready in X" ← time includes config loading
4. getRequestHandlers() → loadConfig #2
```
**After:**
```
1. logStartInfo() → logs version, URLs, envInfo (no config needed)
2. "Ready in X" ← reflects actual framework startup time
3. getRequestHandlers() → loadConfig (once)
4. logExperimentalInfo() → logs experimental features
```
**Changes:**
- Split `logStartInfo` into two functions:
- `logStartInfo()` - basic info (version, URLs, env) - no config needed
- `logExperimentalInfo()` - experimental features and cacheComponents
- Removed `getStartServerInfo()` which was loading config unnecessarily
- Added `experimentalFeatures` and `cacheComponents` to
`ServerInitResult` so they can be passed back after config loads
- In build, added `reportExperimentalFeatures` callback to the existing
`loadConfig` call instead of loading config twice
---------
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Release notes: https://github.com/lovell/sharp/releases/tag/v0.34.5
- Upgrade to libvips v8.17.3 for upstream bug fixes.
- Add experimental support for prebuilt Linux RISC-V 64-bit binaries.
- Support building from source with npm v12+
- Add support for BigTIFF output.
- Improve error messaging when only warnings issued.
- Simplify ICC processing when retaining input profiles.
## What
Adds a `--experimental-cpu-prof` flag to `next dev`, `next build`, and
`next start` commands to capture V8 CPU profiles for debugging
performance bottlenecks.
## Why
When investigating slow builds, slow dev server startups, or production
server performance issues, having access to CPU profiles is invaluable.
This provides a first-party way to capture these profiles without
needing to manually set up the V8 inspector.
## How
- Adds `--experimental-cpu-prof` flag to CLI commands
- Uses V8's built-in CPU profiler via Node.js inspector module
- Profiles are saved to `.next/cpu-profiles/` with descriptive filenames
- Profiles are saved on process exit (Ctrl+C, SIGTERM, or normal exit)
### Profile files generated
**`next dev`:**
- `dev-main-*` - Parent process (dev server orchestration)
- `dev-server-*` - Child server process (request handling and rendering)
**`next build` (Turbopack):**
- `build-main-*` - Main build orchestration process
- `build-turbopack-*` - Turbopack compilation worker
**`next build` (Webpack):**
- `build-main-*` - Main build orchestration process
- `build-webpack-client-*` - Client bundle compilation worker
- `build-webpack-server-*` - Server bundle compilation worker
- `build-webpack-edge-server-*` - Edge runtime compilation worker
**`next start`:**
- `start-main-*` - Production server process
## Changes addressing PR review comments
- Removed signal handlers from `cpu-profile.ts` to prevent conflicts
with CLI cleanup logic (telemetry, traces, etc.)
- Added synchronous exit handler for non-signal process exits (errors,
`process.exit()` calls)
- CLI commands now explicitly call `saveCpuProfile()` as part of their
cleanup before exiting
- Replaced raw ANSI escape codes with `picocolors` library
- Added comprehensive documentation about profile file naming for each
command
- Added build profiling test that verifies correct profile generation
for both Turbopack and Webpack modes
---------
Co-authored-by: Tim Neutkens <tim@timneutkens.nl>
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.
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)