From 84f1e44d4d055ce8326cc96d5bb39eaf8dbba523 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 30 Apr 2026 10:01:06 +0200 Subject: [PATCH 1/5] Deprecate edge runtime (#93369) Note: re-apply of #92817 ### What? Deprecates the Edge Runtime and the `preferredRegion` route segment config that only applied to edge runtime with build-time warnings, TypeScript IDE hints, error documentation pages, and documentation updates. ### Why? Edge Runtime in Next.js is being deprecated in favor of the Node.js runtime (the default). The `preferredRegion` route segment config is tightly coupled to Edge Runtime and is also being deprecated. ### How? **Edge Runtime deprecation:** - Added `warnAboutEdgeRuntime()` helper using `Log.warnOnce` in `packages/next/src/build/warn-about-edge-runtime.ts` - Warning fires in `get-page-static-info.ts` (Webpack dev/build + Turbopack build) and `turbopack-utils.ts` (Turbopack dev) when `runtime = 'edge'` is detected - Marked `'edge'` as `@deprecated` in the TypeScript language service plugin (`rules/config.ts`) - Created `errors/edge-runtime-deprecated.mdx` with migration guidance - Updated documentation across 11 files to add deprecation notices or simplify/remove outdated Edge Runtime references **`preferredRegion` deprecation:** - Added `warnAboutPreferredRegion()` helper in the same file - Warning fires in `get-page-static-info.ts` for both App Router (`config.preferredRegion`) and Pages Router (`config.config?.regions`) - Marked `preferredRegion` as `@deprecated` in the TypeScript language service plugin and the generated types (`next-types-plugin`) - Created `errors/preferred-region-deprecated.mdx` with migration guidance (remove the export) - Updated documentation across 4 files to add deprecation notices **Tests:** - Added `test/e2e/edge-runtime-deprecated/` with fixture and assertions for the edge runtime warning (dev + production) - Added `test/production/preferred-region-deprecated/` with fixture and assertions for the preferredRegion warning (production only -- Turbopack dev does not surface `preferredRegion` from the Rust layer to JS) --------- Co-authored-by: Vercel --- docs/01-app/02-guides/authentication.mdx | 4 +- .../caching-without-cache-components.mdx | 2 +- .../migrating-to-cache-components.mdx | 2 +- docs/01-app/02-guides/self-hosting.mdx | 2 - .../02-route-segment-config/index.mdx | 12 +++--- .../preferredRegion.mdx | 6 ++- .../02-route-segment-config/runtime.mdx | 8 ++-- .../03-file-conventions/route.mdx | 4 +- .../05-config/01-next-config-js/turbopack.mdx | 2 +- .../07-adapters/08-invoking-entrypoints.mdx | 4 +- .../07-adapters/09-output-types.mdx | 12 +++--- errors/edge-runtime-deprecated.mdx | 23 ++++++++++++ errors/preferred-region-deprecated.mdx | 21 +++++++++++ .../build/analysis/get-page-static-info.ts | 20 ++++++++++ .../next/src/build/warn-about-edge-runtime.ts | 19 ++++++++++ .../plugins/next-types-plugin/index.ts | 1 + .../next/src/server/dev/turbopack-utils.ts | 5 +++ .../src/server/typescript/rules/config.ts | 10 ++--- test/cache-components-tests-manifest.json | 1 + .../app/edge-route/route.ts | 5 +++ .../edge-runtime-deprecated/app/layout.tsx | 11 ++++++ test/e2e/edge-runtime-deprecated/app/page.tsx | 3 ++ .../edge-runtime-deprecated.test.ts | 37 +++++++++++++++++++ .../app/layout.tsx | 11 ++++++ .../preferred-region-deprecated/app/page.tsx | 3 ++ .../app/region-route/route.ts | 5 +++ .../preferred-region-deprecated.test.ts | 26 +++++++++++++ 27 files changed, 226 insertions(+), 33 deletions(-) create mode 100644 errors/edge-runtime-deprecated.mdx create mode 100644 errors/preferred-region-deprecated.mdx create mode 100644 packages/next/src/build/warn-about-edge-runtime.ts create mode 100644 test/e2e/edge-runtime-deprecated/app/edge-route/route.ts create mode 100644 test/e2e/edge-runtime-deprecated/app/layout.tsx create mode 100644 test/e2e/edge-runtime-deprecated/app/page.tsx create mode 100644 test/e2e/edge-runtime-deprecated/edge-runtime-deprecated.test.ts create mode 100644 test/production/preferred-region-deprecated/app/layout.tsx create mode 100644 test/production/preferred-region-deprecated/app/page.tsx create mode 100644 test/production/preferred-region-deprecated/app/region-route/route.ts create mode 100644 test/production/preferred-region-deprecated/preferred-region-deprecated.test.ts diff --git a/docs/01-app/02-guides/authentication.mdx b/docs/01-app/02-guides/authentication.mdx index 1fbc5b14c6e8..7afb2c792e77 100644 --- a/docs/01-app/02-guides/authentication.mdx +++ b/docs/01-app/02-guides/authentication.mdx @@ -561,7 +561,7 @@ const secretKey = process.env.SESSION_SECRET #### 2. Encrypting and decrypting sessions -Next, you can use your preferred [session management library](#session-management-libraries) to encrypt and decrypt sessions. Continuing from the previous example, we'll use [Jose](https://www.npmjs.com/package/jose) (compatible with the [Edge Runtime](/docs/app/api-reference/edge)) and React's [`server-only`](https://www.npmjs.com/package/server-only) package to ensure that your session management logic is only executed on the server. +Next, you can use your preferred [session management library](#session-management-libraries) to encrypt and decrypt sessions. Continuing from the previous example, we'll use [Jose](https://www.npmjs.com/package/jose) and React's [`server-only`](https://www.npmjs.com/package/server-only) package to ensure that your session management logic is only executed on the server. ```tsx filename="app/lib/session.ts" switcher import 'server-only' @@ -1121,7 +1121,7 @@ While Proxy can be useful for initial checks, it should not be your only line of > **Tips**: > > - In Proxy, you can also read cookies using `req.cookies.get('session').value`. -> - Proxy uses the Node.js runtime, check if your Auth library and session management library are compatible. You may need to use [Middleware](https://github.com/vercel/next.js/blob/v15.5.6/docs/01-app/03-api-reference/03-file-conventions/middleware.mdx) if your Auth library only supports [Edge Runtime](/docs/app/api-reference/edge) +> - Proxy uses the Node.js runtime, check if your Auth library and session management library are compatible. > - You can use the `matcher` property in the Proxy to specify which routes Proxy should run on. Although, for auth, it's recommended Proxy runs on all routes. diff --git a/docs/01-app/02-guides/caching-without-cache-components.mdx b/docs/01-app/02-guides/caching-without-cache-components.mdx index c3fc250f32be..062018400ea1 100644 --- a/docs/01-app/02-guides/caching-without-cache-components.mdx +++ b/docs/01-app/02-guides/caching-without-cache-components.mdx @@ -183,7 +183,7 @@ export const revalidate = false > **Good to know**: > > - The revalidate value needs to be statically analyzable. For example `revalidate = 600` is valid, but `revalidate = 60 * 10` is not. -> - The revalidate value is not available when using `runtime = 'edge'`. +> - The revalidate value is not available when using the deprecated `runtime = 'edge'`. > - In Development, Pages are _always_ rendered on-demand and are never cached. This allows you to see changes immediately without waiting for a revalidation period to pass. #### Revalidation frequency diff --git a/docs/01-app/02-guides/migrating-to-cache-components.mdx b/docs/01-app/02-guides/migrating-to-cache-components.mdx index dd7f7db9b091..dff0f4398034 100644 --- a/docs/01-app/02-guides/migrating-to-cache-components.mdx +++ b/docs/01-app/02-guides/migrating-to-cache-components.mdx @@ -178,7 +178,7 @@ export default async function Page() { ## `runtime = 'edge'` -**Not supported.** Cache Components requires the Node.js runtime. Switch to the Node.js runtime (the default) by removing the `runtime = 'edge'` export. If you need edge behavior for specific routes, use [Proxy](/docs/app/api-reference/file-conventions/proxy) instead. +**Not supported.** Cache Components requires the Node.js runtime. Switch to the Node.js runtime (the default) by removing the [deprecated](/docs/messages/edge-runtime-deprecated) `runtime = 'edge'` export. If you need edge behavior for specific routes, use [Proxy](/docs/app/api-reference/file-conventions/proxy) instead. ## UI state preservation diff --git a/docs/01-app/02-guides/self-hosting.mdx b/docs/01-app/02-guides/self-hosting.mdx index d0273b919fa9..2917c783472e 100644 --- a/docs/01-app/02-guides/self-hosting.mdx +++ b/docs/01-app/02-guides/self-hosting.mdx @@ -30,8 +30,6 @@ Image Optimization can be used with a [static export](/docs/app/guides/static-ex [Proxy](/docs/app/api-reference/file-conventions/proxy) works self-hosted with zero configuration when deploying using `next start`. Since it requires access to the incoming request, it is not supported when using a [static export](/docs/app/guides/static-exports). -Proxy uses the [Edge runtime](/docs/app/api-reference/edge), a subset of all available Node.js APIs to help ensure low latency, since it may run in front of every route or asset in your application. If you do not want this, you can use the [full Node.js runtime](/blog/next-15-2#nodejs-middleware-experimental) to run Proxy. - If you are looking to add logic (or use an external package) that requires all Node.js APIs, you might be able to move this logic to a [layout](/docs/app/api-reference/file-conventions/layout) as a [Server Component](/docs/app/getting-started/server-and-client-components). For example, checking [headers](/docs/app/api-reference/functions/headers) and [redirecting](/docs/app/api-reference/functions/redirect). You can also use headers, cookies, or query parameters to [redirect](/docs/app/api-reference/config/next-config-js/redirects#header-cookie-and-query-matching) or [rewrite](/docs/app/api-reference/config/next-config-js/rewrites#header-cookie-and-query-matching) through `next.config.js`. If that does not work, you can also use a [custom server](/docs/pages/guides/custom-server). ## Environment Variables diff --git a/docs/01-app/03-api-reference/03-file-conventions/02-route-segment-config/index.mdx b/docs/01-app/03-api-reference/03-file-conventions/02-route-segment-config/index.mdx index d9c308413df0..b537ed830866 100644 --- a/docs/01-app/03-api-reference/03-file-conventions/02-route-segment-config/index.mdx +++ b/docs/01-app/03-api-reference/03-file-conventions/02-route-segment-config/index.mdx @@ -5,12 +5,12 @@ description: Learn about how to configure options for Next.js route segments. The Route Segment Config options allow you to configure the behavior of a [Page](/docs/app/api-reference/file-conventions/page), [Layout](/docs/app/api-reference/file-conventions/layout), or [Route Handler](/docs/app/api-reference/file-conventions/route) by directly exporting the following variables: -| Option | Type | Default | -| -------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | -------------------------- | -| [`dynamicParams`](/docs/app/api-reference/file-conventions/route-segment-config/dynamicParams) | `boolean` | `true` | -| [`runtime`](/docs/app/api-reference/file-conventions/route-segment-config/runtime) | `'nodejs' \| 'edge'` | `'nodejs'` | -| [`preferredRegion`](/docs/app/api-reference/file-conventions/route-segment-config/preferredRegion) | `'auto' \| 'global' \| 'home' \| string \| string[]` | `'auto'` | -| [`maxDuration`](/docs/app/api-reference/file-conventions/route-segment-config/maxDuration) | `number` | Set by deployment platform | +| Option | Type | Default | +| -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | -------------------------- | +| [`dynamicParams`](/docs/app/api-reference/file-conventions/route-segment-config/dynamicParams) | `boolean` | `true` | +| [`runtime`](/docs/app/api-reference/file-conventions/route-segment-config/runtime) | `'nodejs' \| 'edge' (deprecated)` | `'nodejs'` | +| [`preferredRegion`](/docs/app/api-reference/file-conventions/route-segment-config/preferredRegion) | `'auto' \| 'global' \| 'home' \| string \| string[] (deprecated)` | `'auto'` | +| [`maxDuration`](/docs/app/api-reference/file-conventions/route-segment-config/maxDuration) | `number` | Set by deployment platform | ## Version History diff --git a/docs/01-app/03-api-reference/03-file-conventions/02-route-segment-config/preferredRegion.mdx b/docs/01-app/03-api-reference/03-file-conventions/02-route-segment-config/preferredRegion.mdx index e307a6558b74..6998aba9a77a 100644 --- a/docs/01-app/03-api-reference/03-file-conventions/02-route-segment-config/preferredRegion.mdx +++ b/docs/01-app/03-api-reference/03-file-conventions/02-route-segment-config/preferredRegion.mdx @@ -1,8 +1,10 @@ --- -title: preferredRegion +title: preferredRegion (deprecated) description: API reference for the preferredRegion route segment config option. --- +> **Deprecated:** The `preferredRegion` route segment config is deprecated. Remove the `preferredRegion` export from your route files. See the [deprecation message](/docs/messages/preferred-region-deprecated) for details. + The `preferredRegion` option allows you to specify the preferred deployment region for a route segment. This value is passed to your deployment platform. ```tsx filename="layout.tsx | page.tsx | route.ts" switcher @@ -24,7 +26,7 @@ export const preferredRegion = // string || string[] ## Vercel -If deploying Next.js on Vercel, regions are only supported if `export const runtime = 'edge'` is set. The following options can be passed: +If deploying Next.js on Vercel, regions were previously only supported with `export const runtime = 'edge'`, which is now [deprecated](/docs/messages/edge-runtime-deprecated). The following options can be passed: - **`'auto'`** (default): Uses the default region. - **`'global'`**: Prefer deploying the route to all availableregions. diff --git a/docs/01-app/03-api-reference/03-file-conventions/02-route-segment-config/runtime.mdx b/docs/01-app/03-api-reference/03-file-conventions/02-route-segment-config/runtime.mdx index 7634c9240d69..8244d7be193a 100644 --- a/docs/01-app/03-api-reference/03-file-conventions/02-route-segment-config/runtime.mdx +++ b/docs/01-app/03-api-reference/03-file-conventions/02-route-segment-config/runtime.mdx @@ -7,18 +7,18 @@ The `runtime` option allows you to select the JavaScript runtime used for render ```tsx filename="layout.tsx | page.tsx | route.ts" switcher export const runtime = 'nodejs' -// 'nodejs' | 'edge' +// 'nodejs' ``` ```js filename="layout.js | page.js | route.js" switcher export const runtime = 'nodejs' -// 'nodejs' | 'edge' +// 'nodejs' ``` - **`'nodejs'`** (default) -- **`'edge'`** +- **`'edge'`** (deprecated) > **Good to know**: > -> - Using `runtime: 'edge'` is **not supported** for Cache Components. +> - The Edge Runtime is deprecated. Remove the `runtime` export from your route files. See [Edge Runtime Deprecated](/docs/messages/edge-runtime-deprecated). > - This option cannot be used in [Proxy](/docs/app/api-reference/file-conventions/proxy). diff --git a/docs/01-app/03-api-reference/03-file-conventions/route.mdx b/docs/01-app/03-api-reference/03-file-conventions/route.mdx index 5e80618c6b09..5a95a2500dfe 100644 --- a/docs/01-app/03-api-reference/03-file-conventions/route.mdx +++ b/docs/01-app/03-api-reference/03-file-conventions/route.mdx @@ -647,7 +647,7 @@ export const dynamicParams = true export const revalidate = false export const fetchCache = 'auto' export const runtime = 'nodejs' -export const preferredRegion = 'auto' +export const preferredRegion = 'auto' // deprecated ``` ```js filename="app/items/route.js" switcher @@ -656,7 +656,7 @@ export const dynamicParams = true export const revalidate = false export const fetchCache = 'auto' export const runtime = 'nodejs' -export const preferredRegion = 'auto' +export const preferredRegion = 'auto' // deprecated ``` See the [API reference](/docs/app/api-reference/file-conventions/route-segment-config) for more details. diff --git a/docs/01-app/03-api-reference/05-config/01-next-config-js/turbopack.mdx b/docs/01-app/03-api-reference/05-config/01-next-config-js/turbopack.mdx index 3e85c30e21c8..af10aed5741a 100644 --- a/docs/01-app/03-api-reference/05-config/01-next-config-js/turbopack.mdx +++ b/docs/01-app/03-api-reference/05-config/01-next-config-js/turbopack.mdx @@ -229,7 +229,7 @@ In addition, a number of built-in conditions are supported: - `development`: Matches when using `next dev`. - `production`: Matches when using `next build`. - `node`: Matches code that will run on the default Node.js runtime. -- `edge-light`: Matches code that will run on the [Edge runtime](/docs/app/api-reference/edge). +- `edge-light`: Matches code that will run on the [Edge runtime](/docs/app/api-reference/edge) (deprecated). Rules can be an object or an array of objects. An array is often useful for modeling disjoint conditions: diff --git a/docs/01-app/03-api-reference/07-adapters/08-invoking-entrypoints.mdx b/docs/01-app/03-api-reference/07-adapters/08-invoking-entrypoints.mdx index 5fd30e9f44e9..0e258831178f 100644 --- a/docs/01-app/03-api-reference/07-adapters/08-invoking-entrypoints.mdx +++ b/docs/01-app/03-api-reference/07-adapters/08-invoking-entrypoints.mdx @@ -48,7 +48,9 @@ Relevant files in the Next.js core: - [`packages/next/src/build/templates/app-route.ts`](https://github.com/vercel/next.js/blob/canary/packages/next/src/build/templates/app-route.ts) - and [`packages/next/src/build/templates/pages-api.ts`](https://github.com/vercel/next.js/blob/canary/packages/next/src/build/templates/pages-api.ts) -## Edge runtime (`runtime: 'edge'`) +## Edge runtime (`runtime: 'edge'`) (deprecated) + +> The Edge Runtime is [deprecated](/docs/messages/edge-runtime-deprecated). New routes should use the Node.js runtime. Edge entrypoints use the following interface: diff --git a/docs/01-app/03-api-reference/07-adapters/09-output-types.mdx b/docs/01-app/03-api-reference/07-adapters/09-output-types.mdx index 7923f07e1fd6..cc9c8dc47a90 100644 --- a/docs/01-app/03-api-reference/07-adapters/09-output-types.mdx +++ b/docs/01-app/03-api-reference/07-adapters/09-output-types.mdx @@ -15,7 +15,7 @@ The `outputs` object contains arrays of build output types: > **Note:** When `config.output` is set to `'export'`, only `outputs.staticFiles` is populated. All other arrays (`pages`, `appPages`, `pagesApi`, `appRoutes`, `prerenders`) will be empty since the entire application is exported as static files. -For any route output with `runtime: 'edge'`, `edgeRuntime` is included and contains the canonical entry metadata for invoking that output in your edge runtime. +For any route output with `runtime: 'edge'`, `edgeRuntime` is included and contains the canonical entry metadata for invoking that output in your edge runtime. Note that the Edge Runtime is [deprecated](/docs/messages/edge-runtime-deprecated). ## Pages (`outputs.pages`) @@ -38,7 +38,7 @@ React pages from the `pages/` directory: } config: { maxDuration?: number // Maximum duration of the route in seconds - preferredRegion?: string | string[] // Preferred deployment region + preferredRegion?: string | string[] // Preferred deployment region (deprecated) env?: Record // Environment variables (edge runtime only) } } @@ -65,7 +65,7 @@ API routes from `pages/api/`: } config: { maxDuration?: number // Maximum duration of the route in seconds - preferredRegion?: string | string[] // Preferred deployment region + preferredRegion?: string | string[] // Preferred deployment region (deprecated) env?: Record // Environment variables (edge runtime only) } } @@ -92,7 +92,7 @@ React pages from the `app/` directory: } config: { maxDuration?: number // Maximum duration of the route in seconds - preferredRegion?: string | string[] // Preferred deployment region + preferredRegion?: string | string[] // Preferred deployment region (deprecated) env?: Record // Environment variables (edge runtime only) } } @@ -119,7 +119,7 @@ API and metadata routes from the `app/` directory: } config: { maxDuration?: number // Maximum duration of the route in seconds - preferredRegion?: string | string[] // Preferred deployment region + preferredRegion?: string | string[] // Preferred deployment region (deprecated) env?: Record // Environment variables (edge runtime only) } } @@ -194,7 +194,7 @@ Static assets and auto-statically optimized pages: } config: { maxDuration?: number // Maximum duration of the route in seconds - preferredRegion?: string | string[] // Preferred deployment region + preferredRegion?: string | string[] // Preferred deployment region (deprecated) env?: Record // Environment variables (edge runtime only) matchers?: Array<{ source: string // Source pattern diff --git a/errors/edge-runtime-deprecated.mdx b/errors/edge-runtime-deprecated.mdx new file mode 100644 index 000000000000..cbac80507b4e --- /dev/null +++ b/errors/edge-runtime-deprecated.mdx @@ -0,0 +1,23 @@ +--- +title: Edge Runtime Deprecated +--- + +## Why This Warning Occurred + +One or more routes in your application use `export const runtime = 'edge'`, which is deprecated. + +## How to Migrate + +Remove the `runtime` export from your route files: + +```diff +- export const runtime = 'edge' +``` + +The Node.js runtime is the default, so no replacement is needed. + +This applies to all route files that support the `runtime` segment config: `page.ts`, `layout.ts`, `route.ts`, and API routes. + +## Useful Links + +- [Route Segment Config](/docs/app/api-reference/file-conventions/route-segment-config/runtime) diff --git a/errors/preferred-region-deprecated.mdx b/errors/preferred-region-deprecated.mdx new file mode 100644 index 000000000000..b79aed8c5e71 --- /dev/null +++ b/errors/preferred-region-deprecated.mdx @@ -0,0 +1,21 @@ +--- +title: preferredRegion Deprecated +--- + +## Why This Warning Occurred + +One or more routes in your application use `export const preferredRegion`, which is deprecated. + +## How to Migrate + +Remove the `preferredRegion` export from your route files: + +```diff +- export const preferredRegion = 'home' +``` + +This applies to all route files that support the `preferredRegion` segment config: `page.ts`, `layout.ts`, and `route.ts`. + +## Useful Links + +- [Route Segment Config](/docs/app/api-reference/file-conventions/route-segment-config/preferredRegion) diff --git a/packages/next/src/build/analysis/get-page-static-info.ts b/packages/next/src/build/analysis/get-page-static-info.ts index 7e0e7dc92fd6..0862916edc14 100644 --- a/packages/next/src/build/analysis/get-page-static-info.ts +++ b/packages/next/src/build/analysis/get-page-static-info.ts @@ -15,6 +15,10 @@ import { import { tryToParsePath } from '../../lib/try-to-parse-path' import { isAPIRoute } from '../../lib/is-api-route' import { isEdgeRuntime } from '../../lib/is-edge-runtime' +import { + warnAboutEdgeRuntime, + warnAboutPreferredRegion, +} from '../warn-about-edge-runtime' import { RSC_MODULE_TYPES } from '../../shared/lib/constants' import type { RSCMeta } from '../webpack/loaders/get-module-build-info' import { PAGE_TYPES } from '../../lib/page-types' @@ -721,6 +725,14 @@ export async function getAppPageStaticInfo({ ) } + if (isEdgeRuntime(config.runtime)) { + warnAboutEdgeRuntime() + } + + if (config.preferredRegion !== undefined) { + warnAboutPreferredRegion() + } + return { type: PAGE_TYPES.APP, rsc, @@ -831,6 +843,14 @@ export async function getPagesPageStaticInfo({ } } + if (isEdgeRuntime(resolvedRuntime)) { + warnAboutEdgeRuntime() + } + + if (config.config?.regions !== undefined) { + warnAboutPreferredRegion() + } + return { type: PAGE_TYPES.PAGES, getStaticProps, diff --git a/packages/next/src/build/warn-about-edge-runtime.ts b/packages/next/src/build/warn-about-edge-runtime.ts new file mode 100644 index 000000000000..e23a1c3efc1f --- /dev/null +++ b/packages/next/src/build/warn-about-edge-runtime.ts @@ -0,0 +1,19 @@ +import * as Log from './output/log' + +export function warnAboutEdgeRuntime() { + // Webpack build workers each run in a separate process with their own + // warnOnce cache, so the same warning would be emitted once per worker. + // Suppress in workers; the main build process emits the warning once during + // the "Collecting page data" phase. + if (process.env.NEXT_PRIVATE_BUILD_WORKER) return + Log.warnOnce( + `The Edge Runtime is deprecated. You can use the "nodejs" runtime instead. Learn more: https://nextjs.org/docs/messages/edge-runtime-deprecated` + ) +} + +export function warnAboutPreferredRegion() { + if (process.env.NEXT_PRIVATE_BUILD_WORKER) return + Log.warnOnce( + `The "preferredRegion" route segment config is deprecated. Learn more: https://nextjs.org/docs/messages/preferred-region-deprecated` + ) +} diff --git a/packages/next/src/build/webpack/plugins/next-types-plugin/index.ts b/packages/next/src/build/webpack/plugins/next-types-plugin/index.ts index 0d559d0faf80..c679d218940a 100644 --- a/packages/next/src/build/webpack/plugins/next-types-plugin/index.ts +++ b/packages/next/src/build/webpack/plugins/next-types-plugin/index.ts @@ -78,6 +78,7 @@ checkFields { @@ -124,7 +124,7 @@ const API_DOCS: Record< 'The `runtime` option controls the preferred runtime to render this route.', options: { '"nodejs"': 'Prefer the Node.js runtime.', - '"edge"': 'Prefer the Edge runtime.', + '"edge"': `@deprecated\n\nThe Edge Runtime is deprecated. Use \`"nodejs"\` instead.`, '"experimental-edge"': `@deprecated\n\nThis option is no longer experimental. Use \`edge\` instead.`, } satisfies DocsOptionsObject< FullAppSegmentConfig['runtime'] | 'experimental-edge' diff --git a/test/cache-components-tests-manifest.json b/test/cache-components-tests-manifest.json index 0470ac161e51..049df5cbfcd3 100644 --- a/test/cache-components-tests-manifest.json +++ b/test/cache-components-tests-manifest.json @@ -311,6 +311,7 @@ "test/e2e/edge-configurable-runtime/index.test.ts", "test/e2e/edge-pages-support/edge-document.test.ts", "test/e2e/edge-pages-support/index.test.ts", + "test/e2e/edge-runtime-deprecated/edge-runtime-deprecated.test.ts", "test/e2e/edge-runtime-uses-edge-light-import-specifier-for-packages/edge-runtime-uses-edge-light-import-specifier-for-packages.test.ts", "test/e2e/import-conditions/import-conditions.test.ts", "test/e2e/manual-client-base-path/index.test.ts", diff --git a/test/e2e/edge-runtime-deprecated/app/edge-route/route.ts b/test/e2e/edge-runtime-deprecated/app/edge-route/route.ts new file mode 100644 index 000000000000..1a9d280ae635 --- /dev/null +++ b/test/e2e/edge-runtime-deprecated/app/edge-route/route.ts @@ -0,0 +1,5 @@ +export const runtime = 'edge' + +export function GET() { + return new Response('edge route') +} diff --git a/test/e2e/edge-runtime-deprecated/app/layout.tsx b/test/e2e/edge-runtime-deprecated/app/layout.tsx new file mode 100644 index 000000000000..dbce4ea8e3ae --- /dev/null +++ b/test/e2e/edge-runtime-deprecated/app/layout.tsx @@ -0,0 +1,11 @@ +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} diff --git a/test/e2e/edge-runtime-deprecated/app/page.tsx b/test/e2e/edge-runtime-deprecated/app/page.tsx new file mode 100644 index 000000000000..966ce65038a8 --- /dev/null +++ b/test/e2e/edge-runtime-deprecated/app/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

Hello

+} diff --git a/test/e2e/edge-runtime-deprecated/edge-runtime-deprecated.test.ts b/test/e2e/edge-runtime-deprecated/edge-runtime-deprecated.test.ts new file mode 100644 index 000000000000..f3f18de19fe3 --- /dev/null +++ b/test/e2e/edge-runtime-deprecated/edge-runtime-deprecated.test.ts @@ -0,0 +1,37 @@ +import { nextTestSetup } from 'e2e-utils' +import { retry } from 'next-test-utils' + +const EXPECTED_WARNING = + 'The Edge Runtime is deprecated. You can use the "nodejs" runtime instead.' + +describe('edge-runtime-deprecated', () => { + const { next, isNextDev } = nextTestSetup({ + files: __dirname, + }) + + it('should warn about deprecated edge runtime', async () => { + if (isNextDev) { + // In dev mode, the warning fires when the edge route is first compiled. + await next.fetch('/edge-route') + } + + await retry(async () => { + expect(next.cliOutput).toContain(EXPECTED_WARNING) + }) + }) + + it('should only warn once', async () => { + if (isNextDev) { + // Trigger compilation of the edge route again (already compiled, but + // another request ensures no duplicate warnings). + await next.fetch('/edge-route') + } + + await retry(async () => { + expect(next.cliOutput).toContain(EXPECTED_WARNING) + }) + + const occurrences = next.cliOutput.split(EXPECTED_WARNING).length - 1 + expect(occurrences).toBe(1) + }) +}) diff --git a/test/production/preferred-region-deprecated/app/layout.tsx b/test/production/preferred-region-deprecated/app/layout.tsx new file mode 100644 index 000000000000..dbce4ea8e3ae --- /dev/null +++ b/test/production/preferred-region-deprecated/app/layout.tsx @@ -0,0 +1,11 @@ +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} diff --git a/test/production/preferred-region-deprecated/app/page.tsx b/test/production/preferred-region-deprecated/app/page.tsx new file mode 100644 index 000000000000..966ce65038a8 --- /dev/null +++ b/test/production/preferred-region-deprecated/app/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

Hello

+} diff --git a/test/production/preferred-region-deprecated/app/region-route/route.ts b/test/production/preferred-region-deprecated/app/region-route/route.ts new file mode 100644 index 000000000000..f95d82ba4020 --- /dev/null +++ b/test/production/preferred-region-deprecated/app/region-route/route.ts @@ -0,0 +1,5 @@ +export const preferredRegion = 'home' + +export function GET() { + return new Response('region route') +} diff --git a/test/production/preferred-region-deprecated/preferred-region-deprecated.test.ts b/test/production/preferred-region-deprecated/preferred-region-deprecated.test.ts new file mode 100644 index 000000000000..b2bda0c8cd05 --- /dev/null +++ b/test/production/preferred-region-deprecated/preferred-region-deprecated.test.ts @@ -0,0 +1,26 @@ +import { nextTestSetup } from 'e2e-utils' +import { retry } from 'next-test-utils' + +const EXPECTED_WARNING = + 'The "preferredRegion" route segment config is deprecated.' + +describe('preferred-region-deprecated', () => { + const { next } = nextTestSetup({ + files: __dirname, + }) + + it('should warn about deprecated preferredRegion', async () => { + await retry(async () => { + expect(next.cliOutput).toContain(EXPECTED_WARNING) + }) + }) + + it('should only warn once', async () => { + await retry(async () => { + expect(next.cliOutput).toContain(EXPECTED_WARNING) + }) + + const occurrences = next.cliOutput.split(EXPECTED_WARNING).length - 1 + expect(occurrences).toBe(1) + }) +}) From 92210e34b87aa0c70d6f59af863ad7193b04a07c Mon Sep 17 00:00:00 2001 From: Zana Abdi Date: Thu, 30 Apr 2026 15:11:33 +0330 Subject: [PATCH 2/5] Update 05-server-and-client-components.mdx (#93374) Added an important exception (for when client components wrap server components) under an original line that seems to claim a universal law. --------- Co-authored-by: Joseph Co-authored-by: Joseph --- .../01-getting-started/05-server-and-client-components.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/01-app/01-getting-started/05-server-and-client-components.mdx b/docs/01-app/01-getting-started/05-server-and-client-components.mdx index d1615570d3aa..649cccfc3b7e 100644 --- a/docs/01-app/01-getting-started/05-server-and-client-components.mdx +++ b/docs/01-app/01-getting-started/05-server-and-client-components.mdx @@ -173,7 +173,11 @@ export default function Counter() { `"use client"` is used to declare a **boundary** between the Server and Client module graphs (trees). -Once a file is marked with `"use client"`, **all its imports and child components are considered part of the client bundle**. This means you don't need to add the directive to every component that is intended for the client. +Once a file is marked with `"use client"`, **all of its imports and the components it directly renders are included in the client bundle**. This means you don’t need to add the directive to every component that is intended for the client. + +This behavior applies to components that are part of the Client Component’s [module graph](/docs/app/glossary#module-graph), which includes the modules it imports and the components it renders directly. It does not apply to Server Components passed as children or other props. Those components are not imported into the Client Component’s module graph. They are rendered on the server and passed to the Client Component as rendered output. + +See [Interleaving Server and Client Components](/docs/app/getting-started/server-and-client-components#interleaving-server-and-client-components) for how Server and Client Components can be combined. ### Reducing JS bundle size From acb41ed60697759f9bb1213644acc4b9ad1e403f Mon Sep 17 00:00:00 2001 From: Joseph Date: Thu, 30 Apr 2026 14:03:27 +0200 Subject: [PATCH 3/5] docs: clarify rendering flow in Server/Client interleaving example (#93381) follow up to: https://github.com/vercel/next.js/pull/93374 --- .../01-getting-started/05-server-and-client-components.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/01-app/01-getting-started/05-server-and-client-components.mdx b/docs/01-app/01-getting-started/05-server-and-client-components.mdx index 649cccfc3b7e..4cf024887573 100644 --- a/docs/01-app/01-getting-started/05-server-and-client-components.mdx +++ b/docs/01-app/01-getting-started/05-server-and-client-components.mdx @@ -342,7 +342,7 @@ export default function Page() { } ``` -In this pattern, all Server Components will be rendered on the server ahead of time, including those as props. The resulting RSC payload will contain references of where Client Components should be rendered within the component tree. +In this pattern, Server Components are rendered on the server ahead of time, even when passed as props to Client Components. The React Server Component Payload contains the rendered result of those Server Components, plus placeholders for where Client Components should be rendered and references to their JavaScript files. ### Context providers From 62f0d46774a70b3d8f57cdd337deda0678bf4436 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Thu, 30 Apr 2026 14:23:07 +0200 Subject: [PATCH 4/5] Only log partytown setup error once (#93378) --- crates/next-error-code-swc-plugin/src/lib.rs | 1 - packages/next/src/lib/fatal-error.ts | 1 - .../typescript/getTypeScriptConfiguration.ts | 10 ++-- .../lib/typescript/missingDependencyError.ts | 3 +- .../next/src/lib/verify-partytown-setup.ts | 49 +++++++------------ .../next-server-nft/next-server-nft.test.ts | 1 - 6 files changed, 24 insertions(+), 41 deletions(-) delete mode 100644 packages/next/src/lib/fatal-error.ts diff --git a/crates/next-error-code-swc-plugin/src/lib.rs b/crates/next-error-code-swc-plugin/src/lib.rs index 71f349acb349..cef91ca8e243 100644 --- a/crates/next-error-code-swc-plugin/src/lib.rs +++ b/crates/next-error-code-swc-plugin/src/lib.rs @@ -56,7 +56,6 @@ fn is_error_class_name(name: &str) -> bool { || name == "DecodeError" || name == "DynamicServerError" || name == "ExportError" - || name == "FatalError" || name == "ImageError" || name == "InstantValidationError" || name == "InvariantError" diff --git a/packages/next/src/lib/fatal-error.ts b/packages/next/src/lib/fatal-error.ts deleted file mode 100644 index 199044fb1849..000000000000 --- a/packages/next/src/lib/fatal-error.ts +++ /dev/null @@ -1 +0,0 @@ -export class FatalError extends Error {} diff --git a/packages/next/src/lib/typescript/getTypeScriptConfiguration.ts b/packages/next/src/lib/typescript/getTypeScriptConfiguration.ts index e225c42fed93..82a9e5a33fe5 100644 --- a/packages/next/src/lib/typescript/getTypeScriptConfiguration.ts +++ b/packages/next/src/lib/typescript/getTypeScriptConfiguration.ts @@ -3,7 +3,6 @@ import os from 'os' import path from 'path' import semver from 'next/dist/compiled/semver' -import { FatalError } from '../fatal-error' import isError from '../is-error' function resolvePathAliasTarget(baseUrl: string, target: string): string { @@ -92,9 +91,7 @@ export async function getTypeScriptConfiguration( typescript.sys.readFile ) if (error) { - throw new FatalError( - typescript.formatDiagnostic(error, formatDiagnosticsHost) - ) + throw new Error(typescript.formatDiagnostic(error, formatDiagnosticsHost)) } let configToParse: any = config @@ -185,7 +182,8 @@ export async function getTypeScriptConfiguration( } if (result.errors?.length) { - throw new FatalError( + // TODO: Throw AggregateError for all diagnostics. + throw new Error( typescript.formatDiagnostic(result.errors[0], formatDiagnosticsHost) ) } @@ -194,7 +192,7 @@ export async function getTypeScriptConfiguration( } catch (err) { if (isError(err) && err.name === 'SyntaxError') { const reason = '\n' + (err.message ?? '') - throw new FatalError( + throw new Error( bold( 'Could not parse' + cyan('tsconfig.json') + diff --git a/packages/next/src/lib/typescript/missingDependencyError.ts b/packages/next/src/lib/typescript/missingDependencyError.ts index b3dc119a11a2..c0444291dcf4 100644 --- a/packages/next/src/lib/typescript/missingDependencyError.ts +++ b/packages/next/src/lib/typescript/missingDependencyError.ts @@ -2,7 +2,6 @@ import { bold, cyan, red } from '../picocolors' import { getOxfordCommaList } from '../oxford-comma-list' import type { MissingDependency } from '../has-necessary-dependencies' -import { FatalError } from '../fatal-error' import { getPkgManager } from '../helpers/get-pkg-manager' export function missingDepsError( @@ -21,7 +20,7 @@ export function missingDepsError( ' file from your package root (and any TypeScript files in your app and pages directories).' ) - throw new FatalError( + throw new Error( bold( red( `It looks like you're trying to use TypeScript but do not have the required package(s) installed.` diff --git a/packages/next/src/lib/verify-partytown-setup.ts b/packages/next/src/lib/verify-partytown-setup.ts index eccd7ebfe5cc..05e637b89f01 100644 --- a/packages/next/src/lib/verify-partytown-setup.ts +++ b/packages/next/src/lib/verify-partytown-setup.ts @@ -5,14 +5,13 @@ import path from 'path' import { hasNecessaryDependencies } from './has-necessary-dependencies' import type { NecessaryDependencies } from './has-necessary-dependencies' import { fileExists, FileType } from './file-exists' -import { FatalError } from './fatal-error' import * as Log from '../build/output/log' import { getPkgManager } from './helpers/get-pkg-manager' async function missingDependencyError(dir: string) { const packageManager = getPkgManager(dir) - throw new FatalError( + throw new Error( bold( red( "It looks like you're trying to use Partytown with next/script but do not have the required package(s) installed." @@ -65,35 +64,25 @@ export async function verifyPartytownSetup( dir: string, targetDir: string ): Promise { - try { - const partytownDeps: NecessaryDependencies = hasNecessaryDependencies(dir, [ - { - file: '@builder.io/partytown', - pkg: '@builder.io/partytown', - exportsRestrict: false, - }, - ]) + const partytownDeps: NecessaryDependencies = hasNecessaryDependencies(dir, [ + { + file: '@builder.io/partytown', + pkg: '@builder.io/partytown', + exportsRestrict: false, + }, + ]) - if (partytownDeps.missing?.length > 0) { - await missingDependencyError(dir) - } else { - try { - await copyPartytownStaticFiles(partytownDeps, targetDir) - } catch (err) { - Log.warn( - `Partytown library files could not be copied to the static directory. Please ensure that ${bold( - cyan('@builder.io/partytown') - )} is installed as a dependency.` - ) - } - } - } catch (err) { - // Don't show a stack trace when there is an error due to missing dependencies - if (err instanceof FatalError) { - console.error(err.message) - // Throw to allow finally blocks to run (e.g., telemetry flush) - throw err + if (partytownDeps.missing?.length > 0) { + await missingDependencyError(dir) + } else { + try { + await copyPartytownStaticFiles(partytownDeps, targetDir) + } catch (err) { + Log.warn( + `Partytown library files could not be copied to the static directory. Please ensure that ${bold( + cyan('@builder.io/partytown') + )} is installed as a dependency.` + ) } - throw err } } diff --git a/test/production/next-server-nft/next-server-nft.test.ts b/test/production/next-server-nft/next-server-nft.test.ts index 329534b693e0..8b01ec4303fb 100644 --- a/test/production/next-server-nft/next-server-nft.test.ts +++ b/test/production/next-server-nft/next-server-nft.test.ts @@ -243,7 +243,6 @@ async function readNormalizedNFT(next, name) { "/node_modules/next/dist/lib/download-swc.js", "/node_modules/next/dist/lib/error-telemetry-utils.js", "/node_modules/next/dist/lib/fallback.js", - "/node_modules/next/dist/lib/fatal-error.js", "/node_modules/next/dist/lib/file-exists.js", "/node_modules/next/dist/lib/find-config.js", "/node_modules/next/dist/lib/find-pages-dir.js", From 7c0143064dd0d05f26ad738148d94ea9cc3376ff Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Thu, 30 Apr 2026 14:39:17 +0200 Subject: [PATCH 5/5] [test] Stop building fresh packages for every test (#93380) --- turbo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/turbo.json b/turbo.json index d430ed75ad1a..2ee6357f932c 100644 --- a/turbo.json +++ b/turbo.json @@ -20,7 +20,7 @@ "dependsOn": ["^test-storybook"] }, "pack-for-isolated-tests": { - "dependsOn": ["build"], + "inputs": ["$TURBO_DEFAULT$", "dist/**"], "outputs": ["packed.tgz"] }, "typescript": {},