diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4ca6149a8a4a7..00377d09a11d1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -35,7 +35,8 @@ variables: PNPM_CACHE_FOLDER: $(Pipeline.Workspace)/.pnpm-store PNPM_VERSION: 7.3.0 NEXT_TELEMETRY_DISABLED: '1' - node_version: ^14.19.0 + node_14_version: ^14.19.0 + node_16_version: ^16.8.0 stages: - stage: Test @@ -46,7 +47,7 @@ stages: steps: - task: NodeTool@0 inputs: - versionSpec: $(node_version) + versionSpec: $(node_14_version) displayName: 'Install Node.js' - bash: | @@ -84,7 +85,7 @@ stages: - task: NodeTool@0 inputs: - versionSpec: $(node_version) + versionSpec: $(node_14_version) displayName: 'Install Node.js' - bash: | @@ -107,3 +108,37 @@ stages: - script: node run-tests.js --type unit condition: eq(variables['isDocsOnly'], 'No') displayName: 'Run tests' + + - job: test_integration_app_dir + pool: + vmImage: 'windows-2019' + steps: + - task: NodeTool@0 + inputs: + versionSpec: $(node_16_version) + displayName: 'Install Node.js' + + - bash: | + node scripts/run-for-change.js --not --type docs --exec echo "##vso[task.setvariable variable=isDocsOnly]No" + displayName: 'Check Docs Only Change' + + - script: npm i -g pnpm@$(PNPM_VERSION) + condition: eq(variables['isDocsOnly'], 'No') + + - script: pnpm config set store-dir $(PNPM_CACHE_FOLDER) + condition: eq(variables['isDocsOnly'], 'No') + + - script: pnpm store path + condition: eq(variables['isDocsOnly'], 'No') + + - script: pnpm install && pnpm run build + condition: eq(variables['isDocsOnly'], 'No') + displayName: 'Install and build' + + - script: npx playwright install chromium + condition: eq(variables['isDocsOnly'], 'No') + + - script: | + node run-tests.js -c 1 test/integration/app-dir-basic/test/index.test.js + condition: eq(variables['isDocsOnly'], 'No') + displayName: 'Run tests' diff --git a/docs/advanced-features/react-18/switchable-runtime.md b/docs/advanced-features/react-18/switchable-runtime.md index cdd5268e825dd..db5a74e6acb61 100644 --- a/docs/advanced-features/react-18/switchable-runtime.md +++ b/docs/advanced-features/react-18/switchable-runtime.md @@ -8,21 +8,6 @@ Next.js has two **server runtimes** where you can render parts of your applicati By default, Next.js uses the Node.js runtime. [Middleware](https://nextjs.org/docs/advanced-features/middleware) and [Edge API Routes](https://nextjs.org/docs/api-routes/edge-api-routes) use the Edge runtime. -## Global Runtime Option - -To configure the runtime for your whole application, you can set the experimental option `runtime` in your `next.config.js` file: - -```js -// next.config.js -module.exports = { - experimental: { - runtime: 'experimental-edge', // 'node.js' (default) | experimental-edge - }, -} -``` - -You can detect which runtime you're using by looking at the `process.env.NEXT_RUNTIME` Environment Variable during runtime, and examining the `options.nextRuntime` variable during compilation. - ## Page Runtime Option On each page, you can optionally export a `runtime` config set to either `'nodejs'` or `'experimental-edge'`: @@ -38,22 +23,21 @@ export const config = { } ``` -When both the per-page runtime and global runtime are set, the per-page runtime overrides the global runtime. If the per-page runtime is _not_ set, the global runtime option will be used. - ## Runtime Differences -|   | Node (Server) | Node (Serverless) | Edge | -| --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ----------------- | ---------------- | -| [Cold Boot](https://vercel.com/docs/concepts/get-started/compute#cold-and-hot-boots?utm_source=next-site&utm_medium=docs&utm_campaign=next-website) | / | ~250ms | Instant | -| HTTP Streaming | Yes | Yes | Yes | -| IO | All | All | `fetch` | -| Scalability | / | High | Highest | -| Security | Normal | High | High | -| Latency | Normal | Low | Lowest | -| Code Size | / | 50MB | 1MB | -| NPM Packages | All | All | A smaller subset | +|   | Node (Server) | Node (Serverless) | Edge | +| --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ----------------- | -------------------------------------------------------- | +| Name | `nodejs` | `nodejs` | `edge` or `experimental-edge` if using Next.js Rendering | +| [Cold Boot](https://vercel.com/docs/concepts/get-started/compute#cold-and-hot-boots?utm_source=next-site&utm_medium=docs&utm_campaign=next-website) | / | ~250ms | Instant | +| HTTP Streaming | Yes | Yes | Yes | +| IO | All | All | `fetch` | +| Scalability | / | High | Highest | +| Security | Normal | High | High | +| Latency | Normal | Low | Lowest | +| Code Size | / | 50 MB | 4 MB | +| NPM Packages | All | All | A smaller subset | -Next.js' default runtime configuration is good for most use cases, but there’re still many reasons to change to one runtime over the other one. +Next.js' default runtime configuration is good for most use cases, but there are still many reasons to change to one runtime over the other one. For example, for API routes that rely on native Node.js APIs, they need to run with the Node.js Runtime. However, if an API only uses something like cookie-based authentication, using Middleware and the Edge Runtime will be a better choice due to its lower latency as well as better scalability. @@ -63,7 +47,7 @@ For example, for API routes that rely on native Node.js APIs, they need to run w ```typescript export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } export default (req) => new Response('Hello world!') diff --git a/docs/api-reference/edge-runtime.md b/docs/api-reference/edge-runtime.md index 3471b5c041672..0a2704132b84e 100644 --- a/docs/api-reference/edge-runtime.md +++ b/docs/api-reference/edge-runtime.md @@ -144,7 +144,7 @@ You can relax the check to allow specific files with your Middleware or Edge API ```javascript export const config = { - runtime: 'experimental-edge', // for Edge API Routes only + runtime: 'edge', // for Edge API Routes only unstable_allowDynamic: [ '/lib/utilities.js', // allows a single file '/node_modules/function-bind/**', // use a glob to allow anything in the function-bind 3rd party module diff --git a/docs/api-routes/edge-api-routes.md b/docs/api-routes/edge-api-routes.md index ae9378b6f5060..6546c26351d98 100644 --- a/docs/api-routes/edge-api-routes.md +++ b/docs/api-routes/edge-api-routes.md @@ -2,7 +2,7 @@ description: Edge API Routes enable you to build high performance APIs directly inside your Next.js application. --- -# Edge API Routes (Beta) +# Edge API Routes Edge API Routes enable you to build high performance APIs with Next.js. Using the [Edge Runtime](/docs/api-reference/edge-runtime.md), they are often faster than Node.js-based API Routes. This performance improvement does come with [constraints](/docs/api-reference/edge-runtime.md#unsupported-apis), like not having access to native Node.js APIs. Instead, Edge API Routes are built on standard Web APIs. @@ -14,7 +14,7 @@ Any file inside the folder `pages/api` is mapped to `/api/*` and will be treated ```typescript export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } export default (req) => new Response('Hello world!') @@ -26,7 +26,7 @@ export default (req) => new Response('Hello world!') import type { NextRequest } from 'next/server' export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } export default async function handler(req: NextRequest) { @@ -50,7 +50,7 @@ export default async function handler(req: NextRequest) { import type { NextRequest } from 'next/server' export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } export default async function handler(req: NextRequest) { @@ -75,7 +75,7 @@ export default async function handler(req: NextRequest) { import type { NextRequest } from 'next/server' export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } export default async function handler(req: NextRequest) { @@ -91,7 +91,7 @@ export default async function handler(req: NextRequest) { import { type NextRequest } from 'next/server' export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } export default async function handler(req: NextRequest) { @@ -131,4 +131,6 @@ Edge API Routes use the [Edge Runtime](/docs/api-reference/edge-runtime.md), whe Edge API Routes can [stream responses](/docs/api-reference/edge-runtime.md#web-stream-apis) from the server and run _after_ cached files (e.g. HTML, CSS, JavaScript) have been accessed. Server-side streaming can help improve performance with faster [Time To First Byte (TTFB)](https://web.dev/ttfb/). +> Note: Using [Edge Runtime](/docs/api-reference/edge-runtime.md) with `getServerSideProps` does not give you access to the response object. If you need access to `res`, you should use the [Node.js runtime](/docs/advanced-features/react-18/switchable-runtime.md) by setting `runtime: 'nodejs'`. + View the [supported APIs](/docs/api-reference/edge-runtime.md) and [unsupported APIs](/docs/api-reference/edge-runtime.md#unsupported-apis) for the Edge Runtime. diff --git a/docs/basic-features/data-fetching/get-server-side-props.md b/docs/basic-features/data-fetching/get-server-side-props.md index 3d789e4939445..65a0e8dbf5fa4 100644 --- a/docs/basic-features/data-fetching/get-server-side-props.md +++ b/docs/basic-features/data-fetching/get-server-side-props.md @@ -45,6 +45,18 @@ It can be tempting to reach for an [API Route](/docs/api-routes/introduction.md) Take the following example. An API route is used to fetch some data from a CMS. That API route is then called directly from `getServerSideProps`. This produces an additional call, reducing performance. Instead, directly import the logic used inside your API Route into `getServerSideProps`. This could mean calling a CMS, database, or other API directly from inside `getServerSideProps`. +### getServerSideProps with Edge API Routes + +`getServerSideProps` can be used with both Serverless and Edge Runtimes, and you can set props in both. However, currently in Edge Runtime, you do not have access to the response object. This means that you cannot — for example — add cookies in `getServerSideProps`. To have access to the response object, you should **continue to use the Node.js runtime**, which is the default runtime. + +You can explicitly set the runtime on a [per-page basis](https://nextjs.org/docs/advanced-features/react-18/switchable-runtime#page-runtime-option) by modifying the `config`, for example: + +```js +export const config = { + runtime: 'nodejs', +} +``` + ## Fetching data on the client side If your page contains frequently updating data, and you don’t need to pre-render the data, you can fetch the data on the [client side](/docs/basic-features/data-fetching/client-side.md). An example of this is user-specific data: diff --git a/docs/basic-features/data-fetching/incremental-static-regeneration.md b/docs/basic-features/data-fetching/incremental-static-regeneration.md index a4bc788533d32..b7fb3ebc63eca 100644 --- a/docs/basic-features/data-fetching/incremental-static-regeneration.md +++ b/docs/basic-features/data-fetching/incremental-static-regeneration.md @@ -27,7 +27,7 @@ description: 'Learn how to create or update static pages at runtime with Increme Next.js allows you to create or update static pages _after_ you’ve built your site. Incremental Static Regeneration (ISR) enables you to use static-generation on a per-page basis, **without needing to rebuild the entire site**. With ISR, you can retain the benefits of static while scaling to millions of pages. -> Note: the `experimental-edge` runtime (https://nextjs.org/docs/api-reference/edge-runtime) is currently not compatible with ISR although can leverage `stale-while-revalidate` by setting the `cache-control` header manually. +> Note: The [`experimental-edge` runtime](https://nextjs.org/docs/api-reference/edge-runtime) is currently not compatible with ISR, although can leverage `stale-while-revalidate` by setting the `cache-control` header manually. To use ISR, add the `revalidate` prop to `getStaticProps`: diff --git a/docs/manifest.json b/docs/manifest.json index 15178b9b8f146..1a956ba9bbdc2 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -151,7 +151,7 @@ "path": "/docs/api-routes/response-helpers.md" }, { - "title": "Edge API Routes (Beta)", + "title": "Edge API Routes", "path": "/docs/api-routes/edge-api-routes.md" } ] diff --git a/errors/edge-dynamic-code-evaluation.md b/errors/edge-dynamic-code-evaluation.md index 26a2de05ef172..5003e10e44533 100644 --- a/errors/edge-dynamic-code-evaluation.md +++ b/errors/edge-dynamic-code-evaluation.md @@ -33,7 +33,7 @@ You can relax the check to allow specific files with your Middleware or Edge API ```typescript export const config = { - runtime: 'experimental-edge', // for Edge API Routes only + runtime: 'edge', // for Edge API Routes only unstable_allowDynamic: [ '/lib/utilities.js', // allows a single file '/node_modules/function-bind/**', // use a glob to allow anything in the function-bind 3rd party module diff --git a/errors/middleware-upgrade-guide.md b/errors/middleware-upgrade-guide.md index 6d10bf8472ee3..ca1508b0bb8a4 100644 --- a/errors/middleware-upgrade-guide.md +++ b/errors/middleware-upgrade-guide.md @@ -175,7 +175,7 @@ If you were previously using Middleware to forward headers to an external API, y import { type NextRequest } from 'next/server' export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } export default async function handler(req: NextRequest) { diff --git a/errors/returning-response-body-in-middleware.md b/errors/returning-response-body-in-middleware.md index 344ce1b278f95..dd3ea9a34c6ca 100644 --- a/errors/returning-response-body-in-middleware.md +++ b/errors/returning-response-body-in-middleware.md @@ -1,5 +1,7 @@ # Returning response body in Middleware +> Note: In Next.js v13.0.0 you can now respond to Middleware directly by returning a `NextResponse`. For more information, see [Producing a Response](https://nextjs.org/docs/advanced-features/middleware#producing-a-response). + #### Why This Error Occurred [Middleware](https://nextjs.org/docs/advanced-features/middleware) can no longer produce a response body as of `v12.2+`. diff --git a/examples/with-webassembly/pages/api/edge.ts b/examples/with-webassembly/pages/api/edge.ts index 0e9a30908ae40..21867d663e036 100644 --- a/examples/with-webassembly/pages/api/edge.ts +++ b/examples/with-webassembly/pages/api/edge.ts @@ -13,4 +13,4 @@ export default async function handler() { return new Response(`got: ${number}`) } -export const config = { runtime: 'experimental-edge' } +export const config = { runtime: 'edge' } diff --git a/package.json b/package.json index 77b2d103f9f78..690163865715f 100644 --- a/package.json +++ b/package.json @@ -198,6 +198,7 @@ "release": "6.3.1", "request-promise-core": "1.1.2", "resolve-from": "5.0.0", + "rimraf": "3.0.2", "sass": "1.54.0", "seedrandom": "3.0.5", "selenium-webdriver": "4.0.0-beta.4", diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 1527f63482b47..468e92677fd43 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -45,7 +45,6 @@ "cross-spawn": "6.0.5", "got": "10.7.0", "prompts": "2.1.0", - "rimraf": "3.0.2", "tar": "4.4.10", "update-check": "1.5.4", "validate-npm-package-name": "3.0.0" diff --git a/packages/font/package.json b/packages/font/package.json index 52f2e95ec3968..ef6a0f145d844 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -12,7 +12,7 @@ ], "license": "MIT", "scripts": { - "build": "rm -rf dist && pnpm ncc-fontkit && tsc -d -p tsconfig.json", + "build": "rimraf dist && pnpm ncc-fontkit && tsc -d -p tsconfig.json", "prepublishOnly": "cd ../../ && turbo run build", "dev": "pnpm ncc-fontkit && tsc -d -w -p tsconfig.json", "typescript": "tsec --noEmit -p tsconfig.json", diff --git a/packages/next/build/analysis/extract-const-value.ts b/packages/next/build/analysis/extract-const-value.ts index 209f3e813ae1e..0ae1d5fc7993a 100644 --- a/packages/next/build/analysis/extract-const-value.ts +++ b/packages/next/build/analysis/extract-const-value.ts @@ -204,7 +204,7 @@ function extractValue(node: Node, path?: string[]): any { /** * Extracts the value of an exported const variable named `exportedName` - * (e.g. "export const config = { runtime: 'experimental-edge' }") from swc's AST. + * (e.g. "export const config = { runtime: 'edge' }") from swc's AST. * The value must be one of (or throws UnsupportedValueError): * - string * - boolean diff --git a/packages/next/build/analysis/get-page-static-info.ts b/packages/next/build/analysis/get-page-static-info.ts index 8e153b7fcbacf..63fc708c2c89c 100644 --- a/packages/next/build/analysis/get-page-static-info.ts +++ b/packages/next/build/analysis/get-page-static-info.ts @@ -1,18 +1,20 @@ -import { isServerRuntime } from '../../server/config-shared' import type { NextConfig } from '../../server/config-shared' import type { Middleware, RouteHas } from '../../lib/load-custom-routes' + +import { promises as fs } from 'fs' +import { matcher } from 'next/dist/compiled/micromatch' +import { ServerRuntime } from 'next/types' import { extractExportedConstValue, UnsupportedValueError, } from './extract-const-value' import { parseModule } from './parse-module' -import { promises as fs } from 'fs' -import { tryToParsePath } from '../../lib/try-to-parse-path' import * as Log from '../output/log' import { SERVER_RUNTIME } from '../../lib/constants' -import { ServerRuntime } from 'next/types' import { checkCustomRoutes } from '../../lib/load-custom-routes' -import { matcher } from 'next/dist/compiled/micromatch' +import { tryToParsePath } from '../../lib/try-to-parse-path' +import { isAPIRoute } from '../../lib/is-api-route' +import { isEdgeRuntime } from '../../lib/is-edge-runtime' import { RSC_MODULE_TYPES } from '../../shared/lib/constants' export interface MiddlewareConfig { @@ -229,13 +231,13 @@ function getMiddlewareConfig( return result } -let warnedAboutExperimentalEdgeApiFunctions = false -function warnAboutExperimentalEdgeApiFunctions() { - if (warnedAboutExperimentalEdgeApiFunctions) { +let warnedAboutExperimentalEdge = false +function warnAboutExperimentalEdge() { + if (warnedAboutExperimentalEdge) { return } Log.warn(`You are using an experimental edge runtime, the API might change.`) - warnedAboutExperimentalEdgeApiFunctions = true + warnedAboutExperimentalEdge = true } const warnedUnsupportedValueMap = new Map() @@ -307,7 +309,8 @@ export async function getPageStaticInfo(params: { if ( typeof resolvedRuntime !== 'undefined' && - !isServerRuntime(resolvedRuntime) + resolvedRuntime !== SERVER_RUNTIME.nodejs && + !isEdgeRuntime(resolvedRuntime) ) { const options = Object.values(SERVER_RUNTIME).join(', ') if (typeof resolvedRuntime !== 'string') { @@ -326,15 +329,28 @@ export async function getPageStaticInfo(params: { const requiresServerRuntime = ssr || ssg || pageType === 'app' - resolvedRuntime = - SERVER_RUNTIME.edge === resolvedRuntime - ? SERVER_RUNTIME.edge - : requiresServerRuntime - ? resolvedRuntime || nextConfig.experimental?.runtime - : undefined + resolvedRuntime = isEdgeRuntime(resolvedRuntime) + ? resolvedRuntime + : requiresServerRuntime + ? resolvedRuntime || nextConfig.experimental?.runtime + : undefined - if (resolvedRuntime === SERVER_RUNTIME.edge) { - warnAboutExperimentalEdgeApiFunctions() + if (resolvedRuntime === SERVER_RUNTIME.experimentalEdge) { + warnAboutExperimentalEdge() + } + + if ( + resolvedRuntime === SERVER_RUNTIME.edge && + pageType === 'pages' && + page && + !isAPIRoute(page.replace(/^\/pages\//, '/')) + ) { + const message = `Page ${page} provided runtime 'edge', the edge runtime for rendering is currently experimental. Use runtime 'experimental-edge' instead.` + Log.error(message) + + if (!isDev) { + process.exit(1) + } } const middlewareConfig = getMiddlewareConfig( diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index ae00a6095ff4f..5321c624b3f0f 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -12,13 +12,13 @@ import chalk from 'next/dist/compiled/chalk' import { posix, join } from 'path' import { stringify } from 'querystring' import { - API_ROUTE, PAGES_DIR_ALIAS, ROOT_DIR_ALIAS, APP_DIR_ALIAS, - SERVER_RUNTIME, WEBPACK_LAYERS, } from '../lib/constants' +import { isAPIRoute } from '../lib/is-api-route' +import { isEdgeRuntime } from '../lib/is-edge-runtime' import { APP_CLIENT_INTERNALS, RSC_MODULE_TYPES } from '../shared/lib/constants' import { CLIENT_STATIC_FILES_RUNTIME_AMP, @@ -175,7 +175,7 @@ export function getEdgeServerEntry(opts: { return `next-middleware-loader?${stringify(loaderParams)}!` } - if (opts.page.startsWith('/api/') || opts.page === '/api') { + if (isAPIRoute(opts.page)) { const loaderParams: EdgeFunctionLoaderOptions = { absolutePagePath: opts.absolutePagePath, page: opts.page, @@ -257,8 +257,8 @@ export async function runDependingOnPageType(params: { await params.onEdgeServer() return } - if (params.page.match(API_ROUTE)) { - if (params.pageRuntime === SERVER_RUNTIME.edge) { + if (isAPIRoute(params.page)) { + if (isEdgeRuntime(params.pageRuntime)) { await params.onEdgeServer() return } @@ -279,7 +279,7 @@ export async function runDependingOnPageType(params: { await Promise.all([params.onClient(), params.onServer()]) return } - if (params.pageRuntime === SERVER_RUNTIME.edge) { + if (isEdgeRuntime(params.pageRuntime)) { await Promise.all([params.onClient(), params.onEdgeServer()]) return } diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index a29110bdc0966..b9d09a5a44f90 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -18,7 +18,6 @@ import { PUBLIC_DIR_MIDDLEWARE_CONFLICT, MIDDLEWARE_FILENAME, PAGES_DIR_ALIAS, - SERVER_RUNTIME, } from '../lib/constants' import { fileExists } from '../lib/file-exists' import { findPagesDir } from '../lib/find-pages-dir' @@ -109,6 +108,7 @@ import { writeBuildId } from './write-build-id' import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path' import { NextConfigComplete } from '../server/config-shared' import isError, { NextError } from '../lib/is-error' +import { isEdgeRuntime } from '../lib/is-edge-runtime' import { TelemetryPlugin } from './webpack/plugins/telemetry-plugin' import { MiddlewareManifest } from './webpack/plugins/middleware-plugin' import { recursiveCopy } from '../lib/recursive-copy' @@ -1428,7 +1428,7 @@ export default async function build( try { let edgeInfo: any - if (pageRuntime === SERVER_RUNTIME.edge) { + if (isEdgeRuntime(pageRuntime)) { if (pageType === 'app') { edgeRuntimeAppCount++ } else { @@ -1467,7 +1467,7 @@ export default async function build( if (pageType === 'app' && originalAppPath) { appNormalizedPaths.set(originalAppPath, page) // TODO-APP: handle prerendering with edge - if (pageRuntime === 'experimental-edge') { + if (isEdgeRuntime(pageRuntime)) { isStatic = false isSsg = false } else { @@ -1505,7 +1505,7 @@ export default async function build( ) } } else { - if (pageRuntime === SERVER_RUNTIME.edge) { + if (isEdgeRuntime(pageRuntime)) { if (workerResult.hasStaticProps) { console.warn( `"getStaticProps" is not yet supported fully with "experimental-edge", detected on ${page}` diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index 614270f40c311..11aed2d96bd32 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -20,7 +20,6 @@ import { SERVER_PROPS_GET_INIT_PROPS_CONFLICT, SERVER_PROPS_SSG_CONFLICT, MIDDLEWARE_FILENAME, - SERVER_RUNTIME, } from '../lib/constants' import { MODERN_BROWSERSLIST_TARGET } from '../shared/lib/constants' import prettyBytes from '../lib/pretty-bytes' @@ -33,6 +32,7 @@ import { GetStaticPaths, PageConfig, ServerRuntime } from 'next/types' import { BuildManifest } from '../server/get-page-files' import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-slash' import { UnwrapPromise } from '../lib/coalesced-function' +import { isEdgeRuntime } from '../lib/is-edge-runtime' import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path' import * as Log from './output/log' import { @@ -423,7 +423,7 @@ export async function printTreeView( ? '○' : pageInfo?.isSsg ? '●' - : pageInfo?.runtime === SERVER_RUNTIME.edge + : isEdgeRuntime(pageInfo?.runtime) ? 'ℇ' : 'λ' @@ -1267,7 +1267,7 @@ export async function isPageStatic({ let prerenderFallback: boolean | 'blocking' | undefined let appConfig: AppConfig = {} - if (pageRuntime === SERVER_RUNTIME.edge) { + if (isEdgeRuntime(pageRuntime)) { const runtime = await getRuntimeContext({ paths: edgeInfo.files.map((file: string) => path.join(distDir, file)), env: edgeInfo.env, diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 21209d17a4c18..18bc7e98ebf55 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -10,13 +10,13 @@ import { PAGES_DIR_ALIAS, ROOT_DIR_ALIAS, APP_DIR_ALIAS, - SERVER_RUNTIME, WEBPACK_LAYERS, RSC_MOD_REF_PROXY_ALIAS, } from '../lib/constants' import { EXTERNAL_PACKAGES } from '../lib/server-external-packages' import { fileExists } from '../lib/file-exists' import { CustomRoutes } from '../lib/load-custom-routes.js' +import { isEdgeRuntime } from '../lib/is-edge-runtime' import { CLIENT_STATIC_FILES_RUNTIME_AMP, CLIENT_STATIC_FILES_RUNTIME_MAIN, @@ -628,7 +628,7 @@ export default async function getBaseWebpackConfig( const disableOptimizedLoading = true if (isClient) { - if (config.experimental.runtime === SERVER_RUNTIME.edge) { + if (isEdgeRuntime(config.experimental.runtime)) { Log.warn( 'You are using the experimental Edge Runtime with `experimental.runtime`.' ) diff --git a/packages/next/build/webpack/loaders/next-edge-ssr-loader/render.ts b/packages/next/build/webpack/loaders/next-edge-ssr-loader/render.ts index 6eb8ffa3f09b6..ff616c2f06af5 100644 --- a/packages/next/build/webpack/loaders/next-edge-ssr-loader/render.ts +++ b/packages/next/build/webpack/loaders/next-edge-ssr-loader/render.ts @@ -72,7 +72,7 @@ export function getRender({ pagesType, extendRenderOpts: { buildId, - runtime: SERVER_RUNTIME.edge, + runtime: SERVER_RUNTIME.experimentalEdge, supportsDynamicHTML: true, disableOptimizedLoading: true, serverComponentManifest, diff --git a/packages/next/build/webpack/loaders/next-serverless-loader/index.ts b/packages/next/build/webpack/loaders/next-serverless-loader/index.ts index f11541a03a266..fa5026aa622a8 100644 --- a/packages/next/build/webpack/loaders/next-serverless-loader/index.ts +++ b/packages/next/build/webpack/loaders/next-serverless-loader/index.ts @@ -2,7 +2,7 @@ import devalue from 'next/dist/compiled/devalue' import { join } from 'path' import { parse } from 'querystring' import { webpack } from 'next/dist/compiled/webpack/webpack' -import { API_ROUTE } from '../../../../lib/constants' +import { isAPIRoute } from '../../../../lib/is-api-route' import { isDynamicRoute } from '../../../../shared/lib/router/utils' import { escapeStringRegexp } from '../../../../shared/lib/escape-regexp' import { __ApiPreviewProps } from '../../../../server/api-utils' @@ -88,7 +88,7 @@ const nextServerlessLoader: webpack.LoaderDefinitionFunction = function () { ` : 'const runtimeConfig = {}' - if (page.match(API_ROUTE)) { + if (isAPIRoute(page)) { return ` ${envLoading} ${runtimeConfigImports} diff --git a/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts b/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts index 57e2ff6ee13a3..93a055bd1cf5d 100644 --- a/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts +++ b/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts @@ -509,7 +509,10 @@ export class FlightClientEntryPlugin { // Add for the client compilation // Inject the entry to the client compiler. if (this.dev) { - const pageKey = COMPILER_NAMES.client + bundlePath + const pageKey = (COMPILER_NAMES.client + bundlePath).replace( + /\\/g, + path.posix.sep + ) if (!entries[pageKey]) { entries[pageKey] = { type: EntryTypes.CHILD_ENTRY, diff --git a/packages/next/build/webpack/plugins/flight-types-plugin.ts b/packages/next/build/webpack/plugins/flight-types-plugin.ts index 0213ea62b1fd3..aff833983aa69 100644 --- a/packages/next/build/webpack/plugins/flight-types-plugin.ts +++ b/packages/next/build/webpack/plugins/flight-types-plugin.ts @@ -58,7 +58,11 @@ interface IEntry { dynamicParams?: boolean fetchCache?: 'auto' | 'force-no-store' | 'only-no-store' | 'default-no-store' | 'default-cache' | 'only-cache' | 'force-cache' preferredRegion?: 'auto' | 'home' | 'edge' - ${options.type === 'page' ? "runtime?: 'nodejs' | 'experimental-edge'" : ''} + ${ + options.type === 'page' + ? "runtime?: 'nodejs' | 'experimental-edge' | 'edge'" + : '' + } } // ============= diff --git a/packages/next/export/index.ts b/packages/next/export/index.ts index fe1fcb1809544..a785731a7c48e 100644 --- a/packages/next/export/index.ts +++ b/packages/next/export/index.ts @@ -13,7 +13,7 @@ import { promisify } from 'util' import { AmpPageStatus, formatAmpMessages } from '../build/output/index' import * as Log from '../build/output/log' import createSpinner from '../build/spinner' -import { API_ROUTE, SSG_FALLBACK_EXPORT_ERROR } from '../lib/constants' +import { SSG_FALLBACK_EXPORT_ERROR } from '../lib/constants' import { recursiveCopy } from '../lib/recursive-copy' import { recursiveDelete } from '../lib/recursive-delete' import { @@ -40,6 +40,7 @@ import { denormalizePagePath } from '../shared/lib/page-path/denormalize-page-pa import { loadEnvConfig } from '@next/env' import { PrerenderManifest } from '../build' import { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin' +import { isAPIRoute } from '../lib/is-api-route' import { getPagePath } from '../server/require' import { Span } from '../trace' import { FontConfig } from '../server/font-utils' @@ -243,7 +244,7 @@ export default async function exportApp( // _error is exported as 404.html later on // API Routes are Node.js functions - if (page.match(API_ROUTE)) { + if (isAPIRoute(page)) { hasApiRoutes = true continue } @@ -475,7 +476,7 @@ export default async function exportApp( const filteredPaths = exportPaths.filter( // Remove API routes - (route) => !exportPathMap[route].page.match(API_ROUTE) + (route) => !isAPIRoute(exportPathMap[route].page) ) if (filteredPaths.length !== exportPaths.length) { diff --git a/packages/next/lib/constants.ts b/packages/next/lib/constants.ts index ef1d10b103394..7e0127bc1a7f6 100644 --- a/packages/next/lib/constants.ts +++ b/packages/next/lib/constants.ts @@ -1,8 +1,5 @@ import type { ServerRuntime } from '../types' -// Regex for API routes -export const API_ROUTE = /^\/api(?:\/|$)/ - // Patterns to detect middleware files export const MIDDLEWARE_FILENAME = 'middleware' export const MIDDLEWARE_LOCATION_REGEXP = `(?:src/)?${MIDDLEWARE_FILENAME}` @@ -65,7 +62,8 @@ export const ESLINT_PROMPT_VALUES = [ ] export const SERVER_RUNTIME: Record = { - edge: 'experimental-edge', + edge: 'edge', + experimentalEdge: 'experimental-edge', nodejs: 'nodejs', } diff --git a/packages/next/lib/is-api-route.ts b/packages/next/lib/is-api-route.ts new file mode 100644 index 0000000000000..0e079c8018cc9 --- /dev/null +++ b/packages/next/lib/is-api-route.ts @@ -0,0 +1,3 @@ +export function isAPIRoute(value?: string) { + return value === '/api' || Boolean(value?.startsWith('/api/')) +} diff --git a/packages/next/lib/is-edge-runtime.ts b/packages/next/lib/is-edge-runtime.ts new file mode 100644 index 0000000000000..7d297fb32fb78 --- /dev/null +++ b/packages/next/lib/is-edge-runtime.ts @@ -0,0 +1,8 @@ +import { ServerRuntime } from '../types' +import { SERVER_RUNTIME } from './constants' + +export function isEdgeRuntime(value?: string): value is ServerRuntime { + return ( + value === SERVER_RUNTIME.experimentalEdge || value === SERVER_RUNTIME.edge + ) +} diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 3cf525acb8e55..c31269c652af6 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -33,6 +33,7 @@ import type { FontLoaderManifest } from '../build/webpack/plugins/font-loader-ma import { parse as parseQs } from 'querystring' import { format as formatUrl, parse as parseUrl } from 'url' import { getRedirectStatus } from '../lib/redirect-status' +import { isEdgeRuntime } from '../lib/is-edge-runtime' import { NEXT_BUILTIN_DOCUMENT, STATIC_STATUS_PAGES, @@ -1080,7 +1081,7 @@ export default abstract class Server { } // strip header so we generate HTML still if ( - opts.runtime !== 'experimental-edge' || + !isEdgeRuntime(opts.runtime) || (this.serverOptions as any).webServerConfig ) { for (const param of FLIGHT_PARAMETERS) { @@ -1293,7 +1294,7 @@ export default abstract class Server { }) if ( this.nextConfig.experimental.fetchCache && - (opts.runtime !== 'experimental-edge' || + (!isEdgeRuntime(opts.runtime) || (this.serverOptions as any).webServerConfig) ) { delete req.headers[FETCH_CACHE_HEADER] diff --git a/packages/next/server/config-schema.ts b/packages/next/server/config-schema.ts index 14624f761d3e5..2090cd6e46a0c 100644 --- a/packages/next/server/config-schema.ts +++ b/packages/next/server/config-schema.ts @@ -1,6 +1,7 @@ import { NextConfig } from './config' import type { JSONSchemaType } from 'ajv' import { VALID_LOADERS } from '../shared/lib/image-config' +import { SERVER_RUNTIME } from '../lib/constants' const configSchema = { type: 'object', @@ -353,7 +354,7 @@ const configSchema = { }, runtime: { // automatic typing doesn't like enum - enum: ['experimental-edge', 'nodejs'] as any, + enum: Object.values(SERVER_RUNTIME) as any, type: 'string', }, serverComponentsExternalPackages: { diff --git a/packages/next/server/config-shared.ts b/packages/next/server/config-shared.ts index 0a56331d28ceb..858eed14c5261 100644 --- a/packages/next/server/config-shared.ts +++ b/packages/next/server/config-shared.ts @@ -623,12 +623,6 @@ export async function normalizeConfig(phase: string, config: any) { return await config } -export function isServerRuntime(value?: string): value is ServerRuntime { - return ( - value === undefined || value === 'nodejs' || value === 'experimental-edge' - ) -} - export function validateConfig(userConfig: NextConfig): { errors?: Array | null } { diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index 058f23239ac2e..4052b3d5d9db0 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -31,6 +31,7 @@ import fs from 'fs' import { join, relative, resolve, sep } from 'path' import { IncomingMessage, ServerResponse } from 'http' import { addRequestMeta, getRequestMeta } from './request-meta' +import { isAPIRoute } from '../lib/is-api-route' import { isDynamicRoute } from '../shared/lib/router/utils' import { PAGES_MANIFEST, @@ -1199,7 +1200,7 @@ export default class NextNodeServer extends BaseServer { } const bubbleNoFallback = !!query._nextBubbleNoFallback - if (pathname === '/api' || pathname.startsWith('/api/')) { + if (isAPIRoute(pathname)) { delete query._nextBubbleNoFallback const handled = await this.handleApiRequest(req, res, pathname, query) @@ -1268,7 +1269,7 @@ export default class NextNodeServer extends BaseServer { if (!pageFound && this.dynamicRoutes) { for (const dynamicRoute of this.dynamicRoutes) { params = dynamicRoute.match(pathname) || undefined - if (dynamicRoute.page.startsWith('/api') && params) { + if (isAPIRoute(dynamicRoute.page) && params) { page = dynamicRoute.page pageFound = true break diff --git a/packages/next/server/next-typescript.ts b/packages/next/server/next-typescript.ts index 665b7069b4ba4..2835fd0b3c912 100644 --- a/packages/next/server/next-typescript.ts +++ b/packages/next/server/next-typescript.ts @@ -132,6 +132,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.', '"experimental-edge"': 'Prefer the experimental Edge runtime.', }, link: 'https://beta.nextjs.org/docs/api-reference/segment-config#runtime', diff --git a/packages/next/server/router.ts b/packages/next/server/router.ts index 8a5bd6c4fe2b6..ad72b4ada272e 100644 --- a/packages/next/server/router.ts +++ b/packages/next/server/router.ts @@ -12,6 +12,7 @@ import { getNextInternalQuery, NextUrlWithParsedQuery, } from './request-meta' +import { isAPIRoute } from '../lib/is-api-route' import { getPathMatch } from '../shared/lib/router/utils/path-match' import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-slash' import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path' @@ -374,7 +375,7 @@ export default class Router { if ( pathnameInfo.locale && !route.matchesLocaleAPIRoutes && - pathnameInfo.pathname.match(/^\/api(?:\/|$)/) + isAPIRoute(pathnameInfo.pathname) ) { continue } diff --git a/packages/next/server/web-server.ts b/packages/next/server/web-server.ts index e236f6b881f75..727f01763e1c7 100644 --- a/packages/next/server/web-server.ts +++ b/packages/next/server/web-server.ts @@ -5,17 +5,17 @@ import type { NextParsedUrlQuery, NextUrlWithParsedQuery } from './request-meta' import type { Params } from '../shared/lib/router/utils/route-matcher' import type { PayloadOptions } from './send-payload' import type { LoadComponentsReturnType } from './load-components' -import { NoFallbackError, Options } from './base-server' import type { DynamicRoutes, PageChecker, Route } from './router' import type { NextConfig } from './config-shared' import type { BaseNextRequest, BaseNextResponse } from './base-http' import type { UrlWithParsedQuery } from 'url' -import BaseServer from './base-server' import { byteLength } from './api-utils/web' +import BaseServer, { NoFallbackError, Options } from './base-server' import { generateETag } from './lib/etag' import { addRequestMeta } from './request-meta' import WebResponseCache from './response-cache/web' +import { isAPIRoute } from '../lib/is-api-route' import { getPathMatch } from '../shared/lib/router/utils/path-match' import getRouteFromAssetPath from '../shared/lib/router/utils/get-route-from-asset-path' import { detectDomainLocale } from '../shared/lib/i18n/detect-domain-locale' @@ -307,7 +307,7 @@ export default class NextWebServer extends BaseServer { } const bubbleNoFallback = !!query._nextBubbleNoFallback - if (pathname === '/api' || pathname.startsWith('/api/')) { + if (isAPIRoute(pathname)) { delete query._nextBubbleNoFallback } diff --git a/packages/next/shared/lib/router/router.ts b/packages/next/shared/lib/router/router.ts index cc9e43dd9301b..7fc6446ed4fc4 100644 --- a/packages/next/shared/lib/router/router.ts +++ b/packages/next/shared/lib/router/router.ts @@ -44,6 +44,7 @@ import { removeLocale } from '../../../client/remove-locale' import { removeBasePath } from '../../../client/remove-base-path' import { addBasePath } from '../../../client/add-base-path' import { hasBasePath } from '../../../client/has-base-path' +import { isAPIRoute } from '../../../lib/is-api-route' import { getNextPathnameInfo } from './utils/get-next-pathname-info' import { formatNextPathnameInfo } from './utils/format-next-pathname-info' import { compareRouterStates } from './utils/compare-states' @@ -2135,7 +2136,7 @@ export default class Router implements BaseRouter { } } - if (route === '/api' || route.startsWith('/api/')) { + if (isAPIRoute(route)) { handleHardNavigation({ url: as, router: this }) return new Promise(() => {}) } diff --git a/packages/next/types/index.d.ts b/packages/next/types/index.d.ts index f1186464bb04d..6d1f9d4411a45 100644 --- a/packages/next/types/index.d.ts +++ b/packages/next/types/index.d.ts @@ -23,7 +23,7 @@ import { // @ts-ignore This path is generated at build time and conflicts otherwise import next from '../dist/server/next' -export type ServerRuntime = 'nodejs' | 'experimental-edge' | undefined +export type ServerRuntime = 'nodejs' | 'experimental-edge' | 'edge' | undefined // @ts-ignore This path is generated at build time and conflicts otherwise export { NextConfig } from '../dist/server/config' diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index 8a39adb18544e..917eb33e11974 100644 --- a/packages/react-dev-overlay/package.json +++ b/packages/react-dev-overlay/package.json @@ -12,7 +12,7 @@ "author": "Joe Haddad ", "license": "MIT", "scripts": { - "build": "rm -rf dist && tsc -d -p tsconfig.json", + "build": "rimraf dist && tsc -d -p tsconfig.json", "prepublishOnly": "cd ../../ && turbo run build", "dev": "tsc -d -w -p tsconfig.json", "typescript": "tsec --noEmit -p tsconfig.json" diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index 3778d55c68b35..4c4329d018df9 100644 --- a/packages/react-refresh-utils/package.json +++ b/packages/react-refresh-utils/package.json @@ -12,7 +12,7 @@ "author": "Joe Haddad ", "license": "MIT", "scripts": { - "build": "rm -rf dist && tsc -d -p tsconfig.json", + "build": "rimraf dist && tsc -d -p tsconfig.json", "prepublishOnly": "cd ../../ && turbo run build", "dev": "tsc -d -w -p tsconfig.json" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2101ea16dbc7a..e9d12e0058f49 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -163,6 +163,7 @@ importers: release: 6.3.1 request-promise-core: 1.1.2 resolve-from: 5.0.0 + rimraf: 3.0.2 sass: 1.54.0 seedrandom: 3.0.5 selenium-webdriver: 4.0.0-beta.4 @@ -337,6 +338,7 @@ importers: release: 6.3.1 request-promise-core: 1.1.2_request@2.88.2 resolve-from: 5.0.0 + rimraf: 3.0.2 sass: 1.54.0 seedrandom: 3.0.5 selenium-webdriver: 4.0.0-beta.4 @@ -403,7 +405,6 @@ importers: cross-spawn: 6.0.5 got: 10.7.0 prompts: 2.1.0 - rimraf: 3.0.2 tar: 4.4.10 update-check: 1.5.4 validate-npm-package-name: 3.0.0 @@ -425,7 +426,6 @@ importers: cross-spawn: 6.0.5 got: 10.7.0 prompts: 2.1.0 - rimraf: 3.0.2 tar: 4.4.10 update-check: 1.5.4 validate-npm-package-name: 3.0.0 @@ -9805,7 +9805,7 @@ packages: dev: true /concat-map/0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} /concat-stream/1.6.2: resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} @@ -14291,6 +14291,7 @@ packages: /is-docker/2.0.0: resolution: {integrity: sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==} engines: {node: '>=8'} + dev: true /is-docker/2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} @@ -14642,7 +14643,7 @@ packages: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} dependencies: - is-docker: 2.0.0 + is-docker: 2.2.1 /is-yarn-global/0.3.0: resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==} diff --git a/test/e2e/app-dir/app-middleware/pages/api/dump-headers-edge.js b/test/e2e/app-dir/app-middleware/pages/api/dump-headers-edge.js index 0ece8ea2c7518..05396dccbb6a9 100644 --- a/test/e2e/app-dir/app-middleware/pages/api/dump-headers-edge.js +++ b/test/e2e/app-dir/app-middleware/pages/api/dump-headers-edge.js @@ -1,5 +1,5 @@ export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } export default (req) => { diff --git a/test/e2e/app-dir/app/pages/api/hello.js b/test/e2e/app-dir/app/pages/api/hello.js index 47779ec1fa09f..81adbb05f1024 100644 --- a/test/e2e/app-dir/app/pages/api/hello.js +++ b/test/e2e/app-dir/app/pages/api/hello.js @@ -3,5 +3,5 @@ export default function api(req) { } export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } diff --git a/test/e2e/edge-api-endpoints-can-receive-body/app/pages/api/edge.js b/test/e2e/edge-api-endpoints-can-receive-body/app/pages/api/edge.js index 26bcb1402fe01..306d20a7e2825 100644 --- a/test/e2e/edge-api-endpoints-can-receive-body/app/pages/api/edge.js +++ b/test/e2e/edge-api-endpoints-can-receive-body/app/pages/api/edge.js @@ -7,5 +7,5 @@ export default async (req) => { } export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } diff --git a/test/e2e/edge-api-endpoints-can-receive-body/app/pages/api/index.js b/test/e2e/edge-api-endpoints-can-receive-body/app/pages/api/index.js index 26bcb1402fe01..306d20a7e2825 100644 --- a/test/e2e/edge-api-endpoints-can-receive-body/app/pages/api/index.js +++ b/test/e2e/edge-api-endpoints-can-receive-body/app/pages/api/index.js @@ -7,5 +7,5 @@ export default async (req) => { } export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } diff --git a/test/e2e/edge-async-local-storage/index.test.ts b/test/e2e/edge-async-local-storage/index.test.ts index 39c2798f18224..cb38bfa3c72e1 100644 --- a/test/e2e/edge-async-local-storage/index.test.ts +++ b/test/e2e/edge-async-local-storage/index.test.ts @@ -10,7 +10,7 @@ describe('edge api can use async local storage', () => { { title: 'a single instance', code: ` - export const config = { runtime: 'experimental-edge' } + export const config = { runtime: 'edge' } const storage = new AsyncLocalStorage() export default async function handler(request) { @@ -36,7 +36,7 @@ describe('edge api can use async local storage', () => { { title: 'multiple instances', code: ` - export const config = { runtime: 'experimental-edge' } + export const config = { runtime: 'edge' } const topStorage = new AsyncLocalStorage() export default async function handler(request) { diff --git a/test/e2e/edge-can-use-wasm-files/index.test.ts b/test/e2e/edge-can-use-wasm-files/index.test.ts index 48050bc0c584b..42f8232552c23 100644 --- a/test/e2e/edge-can-use-wasm-files/index.test.ts +++ b/test/e2e/edge-can-use-wasm-files/index.test.ts @@ -49,7 +49,7 @@ describe('edge api endpoints can use wasm files', () => { const value = await increment(input); return new Response(null, { headers: { data: JSON.stringify({ input, value }) } }); } - export const config = { runtime: 'experimental-edge' }; + export const config = { runtime: 'edge' }; `, 'src/add.wasm': new FileRef(path.join(__dirname, './add.wasm')), 'src/add.js': ` diff --git a/test/e2e/edge-compiler-can-import-blob-assets/app/pages/api/edge.js b/test/e2e/edge-compiler-can-import-blob-assets/app/pages/api/edge.js index 49da52df583bd..7d3dc54e193a0 100644 --- a/test/e2e/edge-compiler-can-import-blob-assets/app/pages/api/edge.js +++ b/test/e2e/edge-compiler-can-import-blob-assets/app/pages/api/edge.js @@ -1,4 +1,4 @@ -export const config = { runtime: 'experimental-edge' } +export const config = { runtime: 'edge' } /** * @param {import('next/server').NextRequest} req diff --git a/test/e2e/edge-configurable-runtime/app/pages/api/edge.js b/test/e2e/edge-configurable-runtime/app/pages/api/edge.js new file mode 100644 index 0000000000000..beac5edf5a72d --- /dev/null +++ b/test/e2e/edge-configurable-runtime/app/pages/api/edge.js @@ -0,0 +1,2 @@ +export default () => new Response('ok') +export const config = { runtime: 'edge' } diff --git a/test/e2e/edge-configurable-runtime/app/pages/index.jsx b/test/e2e/edge-configurable-runtime/app/pages/index.jsx new file mode 100644 index 0000000000000..dc344c3effb9f --- /dev/null +++ b/test/e2e/edge-configurable-runtime/app/pages/index.jsx @@ -0,0 +1 @@ +export default () =>

hello world

diff --git a/test/e2e/edge-configurable-runtime/index.test.ts b/test/e2e/edge-configurable-runtime/index.test.ts new file mode 100644 index 0000000000000..d96cddd836b60 --- /dev/null +++ b/test/e2e/edge-configurable-runtime/index.test.ts @@ -0,0 +1,132 @@ +import { createNext, FileRef } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { fetchViaHTTP, File, nextBuild } from 'next-test-utils' +import { join } from 'path' + +const appDir = join(__dirname, './app') +const pagePath = 'pages/index.jsx' +const apiPath = 'pages/api/edge.js' +const page = new File(join(appDir, pagePath)) +const api = new File(join(appDir, apiPath)) + +describe('Configurable runtime for pages and API routes', () => { + let next: NextInstance + + if ((global as any).isNextDev) { + describe('In dev mode', () => { + beforeAll(async () => { + next = await createNext({ + files: new FileRef(appDir), + dependencies: {}, + skipStart: true, + }) + }) + + afterEach(async () => { + await next.stop() + await next.patchFile(pagePath, page.originalContent) + await next.patchFile(apiPath, api.originalContent) + }) + + afterAll(() => next.destroy()) + + it('runs with no warning API route on the edge runtime', async () => { + await next.start() + const res = await fetchViaHTTP(next.url, `/api/edge`) + expect(res.status).toEqual(200) + expect(next.cliOutput).not.toInclude('error') + expect(next.cliOutput).not.toInclude('warn') + }) + + it('warns about API route using experimental-edge runtime', async () => { + await next.patchFile( + apiPath, + ` + export default () => new Response('ok'); + export const config = { runtime: 'experimental-edge' }; + ` + ) + await next.start() + const res = await fetchViaHTTP(next.url, `/api/edge`) + expect(res.status).toEqual(200) + expect(next.cliOutput).not.toInclude('error') + expect(next.cliOutput).toInclude( + 'warn - You are using an experimental edge runtime, the API might change.' + ) + }) + it('warns about page using edge runtime', async () => { + await next.patchFile( + pagePath, + ` + export default () => (

hello world

); + export const runtime = 'experimental-edge'; + ` + ) + await next.start() + const res = await fetchViaHTTP(next.url, `/`) + expect(res.status).toEqual(200) + expect(next.cliOutput).not.toInclude('error') + expect(next.cliOutput).toInclude( + 'warn - You are using an experimental edge runtime, the API might change.' + ) + }) + + it('errors about page using edge runtime', async () => { + await next.patchFile( + pagePath, + ` + export default () => (

hello world

); + export const runtime = 'edge'; + ` + ) + await next.start() + const res = await fetchViaHTTP(next.url, `/`) + expect(res.status).toEqual(200) + expect(next.cliOutput).toInclude( + `error - Page /pages provided runtime 'edge', the edge runtime for rendering is currently experimental. Use runtime 'experimental-edge' instead.` + ) + expect(next.cliOutput).not.toInclude('warn') + }) + }) + } else if ((global as any).isNextStart) { + describe('In start mode', () => { + // TODO because createNext runs process.exit() without any log info on build failure, rely on good old nextBuild() + afterEach(async () => { + page.restore() + api.restore() + }) + + it('builds with API route on the edge runtime and page on the experimental edge runtime', async () => { + page.write(` + export default () => (

hello world

); + export const runtime = 'experimental-edge'; + `) + const output = await nextBuild(appDir, undefined, { + stdout: true, + stderr: true, + }) + expect(output.code).toBe(0) + expect(output.stderr).not.toContain(`error`) + expect(output.stdout).not.toContain(`warn`) + }) + + it('does not build with page on the edge runtime', async () => { + page.write(` + export default () => (

hello world

); + export const runtime = 'edge'; + `) + const output = await nextBuild(appDir, undefined, { + stdout: true, + stderr: true, + }) + expect(output.code).toBe(1) + expect(output.stderr).not.toContain(`Build failed`) + expect(output.stderr).toContain( + `error - Page / provided runtime 'edge', the edge runtime for rendering is currently experimental. Use runtime 'experimental-edge' instead.` + ) + }) + }) + } else { + it.skip('no deploy tests', () => {}) + } +}) diff --git a/test/e2e/middleware-general/app/pages/api/edge-search-params.js b/test/e2e/middleware-general/app/pages/api/edge-search-params.js index 01a968ee9d806..9b8cf7cd547ef 100644 --- a/test/e2e/middleware-general/app/pages/api/edge-search-params.js +++ b/test/e2e/middleware-general/app/pages/api/edge-search-params.js @@ -1,6 +1,6 @@ import { NextResponse } from 'next/server' -export const config = { runtime: 'experimental-edge', regions: 'default' } +export const config = { runtime: 'edge', regions: 'default' } /** * @param {import('next/server').NextRequest} diff --git a/test/e2e/middleware-request-header-overrides/app/pages/api/dump-headers-edge.js b/test/e2e/middleware-request-header-overrides/app/pages/api/dump-headers-edge.js index 0ece8ea2c7518..05396dccbb6a9 100644 --- a/test/e2e/middleware-request-header-overrides/app/pages/api/dump-headers-edge.js +++ b/test/e2e/middleware-request-header-overrides/app/pages/api/dump-headers-edge.js @@ -1,5 +1,5 @@ export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } export default (req) => { diff --git a/test/e2e/og-api/app/pages/api/og.js b/test/e2e/og-api/app/pages/api/og.js index 1afb24a112920..7d744957fc307 100644 --- a/test/e2e/og-api/app/pages/api/og.js +++ b/test/e2e/og-api/app/pages/api/og.js @@ -2,7 +2,7 @@ import { ImageResponse } from '@vercel/og' export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } export default function () { diff --git a/test/e2e/skip-trailing-slash-redirect/app/pages/api/test-cookie-edge.js b/test/e2e/skip-trailing-slash-redirect/app/pages/api/test-cookie-edge.js index 6018a223708fd..212da9b3231a1 100644 --- a/test/e2e/skip-trailing-slash-redirect/app/pages/api/test-cookie-edge.js +++ b/test/e2e/skip-trailing-slash-redirect/app/pages/api/test-cookie-edge.js @@ -1,7 +1,7 @@ import { NextResponse } from 'next/server' export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } export default function handler(req) { diff --git a/test/e2e/streaming-ssr/streaming-ssr/pages/api/user/[id].js b/test/e2e/streaming-ssr/streaming-ssr/pages/api/user/[id].js index c5649d2074e49..67d2491a96f9f 100644 --- a/test/e2e/streaming-ssr/streaming-ssr/pages/api/user/[id].js +++ b/test/e2e/streaming-ssr/streaming-ssr/pages/api/user/[id].js @@ -3,5 +3,5 @@ export default async function handler() { } export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } diff --git a/test/e2e/switchable-runtime/index.test.ts b/test/e2e/switchable-runtime/index.test.ts index 6c5c1e44bf2c5..480f735dd67e7 100644 --- a/test/e2e/switchable-runtime/index.test.ts +++ b/test/e2e/switchable-runtime/index.test.ts @@ -45,15 +45,10 @@ describe('Switchable runtime', () => { beforeAll(async () => { next = await createNext({ - files: { - app: new FileRef(join(__dirname, './app')), - pages: new FileRef(join(__dirname, './pages')), - utils: new FileRef(join(__dirname, './utils')), - 'next.config.js': new FileRef(join(__dirname, './next.config.js')), - }, + files: new FileRef(__dirname), dependencies: { - react: 'experimental', - 'react-dom': 'experimental', + react: 'latest', + 'react-dom': 'latest', }, }) context = { @@ -229,7 +224,7 @@ describe('Switchable runtime', () => { 'pages/api/switch-in-dev.js', ` export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } export default () => new Response('edge response') @@ -259,7 +254,7 @@ describe('Switchable runtime', () => { 'pages/api/switch-in-dev.js', ` export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } export default () => new Response('edge response again') @@ -340,7 +335,7 @@ describe('Switchable runtime', () => { 'pages/api/switch-in-dev-same-content.js', ` export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } export default () => new Response('edge response') @@ -373,7 +368,7 @@ describe('Switchable runtime', () => { 'pages/api/syntax-error-in-dev.js', ` export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } export default => new Response('edge response') @@ -391,7 +386,7 @@ describe('Switchable runtime', () => { export default () => new Response('edge response again') export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } ` diff --git a/test/e2e/switchable-runtime/pages/api/edge.js b/test/e2e/switchable-runtime/pages/api/edge.js index d375a24aaab74..806920e75dfeb 100644 --- a/test/e2e/switchable-runtime/pages/api/edge.js +++ b/test/e2e/switchable-runtime/pages/api/edge.js @@ -3,5 +3,5 @@ export default (req) => { } export const config = { - runtime: `experimental-edge`, + runtime: `edge`, } diff --git a/test/e2e/switchable-runtime/pages/api/hello.js b/test/e2e/switchable-runtime/pages/api/hello.js index 7f27052a3fbc2..c8e368a7530a0 100644 --- a/test/e2e/switchable-runtime/pages/api/hello.js +++ b/test/e2e/switchable-runtime/pages/api/hello.js @@ -3,5 +3,5 @@ export default (req) => { } export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } diff --git a/test/e2e/switchable-runtime/pages/api/syntax-error-in-dev.js b/test/e2e/switchable-runtime/pages/api/syntax-error-in-dev.js index 3b7243a1205ef..9e13e2a85e395 100644 --- a/test/e2e/switchable-runtime/pages/api/syntax-error-in-dev.js +++ b/test/e2e/switchable-runtime/pages/api/syntax-error-in-dev.js @@ -1,5 +1,5 @@ export default () => new Response('edge response') export const config = { - runtime: `experimental-edge`, + runtime: `edge`, } diff --git a/test/integration/app-dir-basic/app/blog/page.js b/test/integration/app-dir-basic/app/blog/page.js new file mode 100644 index 0000000000000..f8a7b0ab42527 --- /dev/null +++ b/test/integration/app-dir-basic/app/blog/page.js @@ -0,0 +1,3 @@ +export default function page() { + return <>blog +} diff --git a/test/integration/app-dir-basic/app/layout.js b/test/integration/app-dir-basic/app/layout.js new file mode 100644 index 0000000000000..747270b45987a --- /dev/null +++ b/test/integration/app-dir-basic/app/layout.js @@ -0,0 +1,8 @@ +export default function RootLayout({ children }) { + return ( + + + {children} + + ) +} diff --git a/test/integration/app-dir-basic/app/page.js b/test/integration/app-dir-basic/app/page.js new file mode 100644 index 0000000000000..8c790ee258cfb --- /dev/null +++ b/test/integration/app-dir-basic/app/page.js @@ -0,0 +1,3 @@ +export default function page() { + return <>page +} diff --git a/test/integration/app-dir-basic/next.config.js b/test/integration/app-dir-basic/next.config.js new file mode 100644 index 0000000000000..cfa3ac3d7aa94 --- /dev/null +++ b/test/integration/app-dir-basic/next.config.js @@ -0,0 +1,5 @@ +module.exports = { + experimental: { + appDir: true, + }, +} diff --git a/test/integration/app-dir-basic/test/index.test.js b/test/integration/app-dir-basic/test/index.test.js new file mode 100644 index 0000000000000..4dd430ba144cf --- /dev/null +++ b/test/integration/app-dir-basic/test/index.test.js @@ -0,0 +1,33 @@ +/* eslint-env jest */ + +import { join } from 'path' +import { runDevSuite, runProdSuite, renderViaHTTP } from 'next-test-utils' + +import webdriver from 'next-webdriver' +const appDir = join(__dirname, '..') + +function runTests(context, env) { + describe('App Dir Basic', () => { + it('should render html properly', async () => { + const indexHtml = await renderViaHTTP(context.appPort, '/') + const blogHtml = await renderViaHTTP(context.appPort, '/blog') + + expect(indexHtml).toContain('page') + expect(blogHtml).toContain('blog') + }) + + it('should hydrate pages properly', async () => { + const browser = await webdriver(context.appPort, '/') + const indexHtml = await browser.waitForElementByCss('body').text() + const url = await browser.url() + await browser.loadPage(url + 'blog') + const blogHtml = await browser.waitForElementByCss('body').text() + + expect(indexHtml).toContain('page') + expect(blogHtml).toContain('blog') + }) + }) +} + +runDevSuite('App Dir Basic', appDir, { runTests }) +runProdSuite('App Dir Basic', appDir, { runTests }) diff --git a/test/integration/edge-runtime-configurable-guards/pages/api/route.js b/test/integration/edge-runtime-configurable-guards/pages/api/route.js index 9d808b1a2bb68..8bb7f51f3b500 100644 --- a/test/integration/edge-runtime-configurable-guards/pages/api/route.js +++ b/test/integration/edge-runtime-configurable-guards/pages/api/route.js @@ -4,5 +4,5 @@ export default () => { } export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } diff --git a/test/integration/edge-runtime-configurable-guards/test/index.test.js b/test/integration/edge-runtime-configurable-guards/test/index.test.js index c55c813b4a8f0..ae0144346d42e 100644 --- a/test/integration/edge-runtime-configurable-guards/test/index.test.js +++ b/test/integration/edge-runtime-configurable-guards/test/index.test.js @@ -72,7 +72,7 @@ describe('Edge runtime configurable guards', () => { return Response.json({ result: true }) } export const config = { - runtime: 'experimental-edge', + runtime: 'edge', unstable_allowDynamic: '/lib/**' } `) @@ -125,7 +125,7 @@ describe('Edge runtime configurable guards', () => { return Response.json({ result: true }) } export const config = { - runtime: 'experimental-edge', + runtime: 'edge', unstable_allowDynamic: '**' } `) @@ -159,7 +159,7 @@ describe('Edge runtime configurable guards', () => { return Response.json({ result: true }) } export const config = { - runtime: 'experimental-edge', + runtime: 'edge', unstable_allowDynamic: '/lib/**' } `) @@ -221,7 +221,7 @@ describe('Edge runtime configurable guards', () => { return Response.json({ result: true }) } export const config = { - runtime: 'experimental-edge', + runtime: 'edge', unstable_allowDynamic: '**' } `) @@ -257,7 +257,7 @@ describe('Edge runtime configurable guards', () => { return Response.json({ result: true }) } export const config = { - runtime: 'experimental-edge', + runtime: 'edge', unstable_allowDynamic: '/lib/**' } `) @@ -328,7 +328,7 @@ describe('Edge runtime configurable guards', () => { return Response.json({ result: true }) } export const config = { - runtime: 'experimental-edge', + runtime: 'edge', unstable_allowDynamic: '/pages/**' } `) @@ -397,7 +397,7 @@ describe('Edge runtime configurable guards', () => { export default async function handler(request) { return Response.json({ result: (() => {}) instanceof Function }) } - export const config = { runtime: 'experimental-edge' } + export const config = { runtime: 'edge' } `) }, }, diff --git a/test/integration/edge-runtime-dynamic-code/pages/api/route.js b/test/integration/edge-runtime-dynamic-code/pages/api/route.js index 1d73702aec603..f0d136912e579 100644 --- a/test/integration/edge-runtime-dynamic-code/pages/api/route.js +++ b/test/integration/edge-runtime-dynamic-code/pages/api/route.js @@ -23,4 +23,4 @@ export default async function handler(request) { ) } -export const config = { runtime: 'experimental-edge' } +export const config = { runtime: 'edge' } diff --git a/test/integration/edge-runtime-module-errors/pages/api/route.js b/test/integration/edge-runtime-module-errors/pages/api/route.js index d93a0ca4a9bad..62ba98a1ceb49 100644 --- a/test/integration/edge-runtime-module-errors/pages/api/route.js +++ b/test/integration/edge-runtime-module-errors/pages/api/route.js @@ -2,4 +2,4 @@ export default async function handler(request) { return Response.json({ ok: true }) } -export const config = { runtime: 'experimental-edge' } +export const config = { runtime: 'edge' } diff --git a/test/integration/edge-runtime-module-errors/test/index.test.js b/test/integration/edge-runtime-module-errors/test/index.test.js index 2906fe6ce0a3d..242e9dc934fa7 100644 --- a/test/integration/edge-runtime-module-errors/test/index.test.js +++ b/test/integration/edge-runtime-module-errors/test/index.test.js @@ -68,7 +68,7 @@ describe('Edge runtime code with imports', () => { return Response.json({ ok: basename() }) } - export const config = { runtime: 'experimental-edge' } + export const config = { runtime: 'edge' } `) }, }, @@ -129,7 +129,7 @@ describe('Edge runtime code with imports', () => { return Response.json({ ok: writeFile() }) } - export const config = { runtime: 'experimental-edge' } + export const config = { runtime: 'edge' } `) }, }, @@ -188,7 +188,7 @@ describe('Edge runtime code with imports', () => { return Response.json({ ok: await throwAsync() }) } - export const config = { runtime: 'experimental-edge' } + export const config = { runtime: 'edge' } `) }, }, @@ -267,7 +267,7 @@ describe('Edge runtime code with imports', () => { return Response.json({ ok: true }) } - export const config = { runtime: 'experimental-edge' } + export const config = { runtime: 'edge' } `) }, }, @@ -325,7 +325,7 @@ describe('Edge runtime code with imports', () => { return Response.json({ ok: true }) } - export const config = { runtime: 'experimental-edge' } + export const config = { runtime: 'edge' } `) }, }, @@ -384,7 +384,7 @@ describe('Edge runtime code with imports', () => { return Response.json({ ok: true }) } - export const config = { runtime: 'experimental-edge' } + export const config = { runtime: 'edge' } `) }, }, @@ -445,7 +445,7 @@ describe('Edge runtime code with imports', () => { return Response.json({ ok: true }) } - export const config = { runtime: 'experimental-edge' } + export const config = { runtime: 'edge' } `) }, }, @@ -503,7 +503,7 @@ describe('Edge runtime code with imports', () => { return response } - export const config = { runtime: 'experimental-edge' } + export const config = { runtime: 'edge' } `) }, }, @@ -564,7 +564,7 @@ describe('Edge runtime code with imports', () => { return response } - export const config = { runtime: 'experimental-edge' } + export const config = { runtime: 'edge' } `) }, }, diff --git a/test/integration/edge-runtime-response-error/pages/api/route.js b/test/integration/edge-runtime-response-error/pages/api/route.js index a1ff06c9a8fc6..6ecf1ee385d91 100644 --- a/test/integration/edge-runtime-response-error/pages/api/route.js +++ b/test/integration/edge-runtime-response-error/pages/api/route.js @@ -2,4 +2,4 @@ export default async function handler(request) { return 'Boom' } -export const config = { runtime: 'experimental-edge' } +export const config = { runtime: 'edge' } diff --git a/test/integration/edge-runtime-streaming-error/pages/api/test.js b/test/integration/edge-runtime-streaming-error/pages/api/test.js index b5d485fcd223a..027f27fe080c9 100644 --- a/test/integration/edge-runtime-streaming-error/pages/api/test.js +++ b/test/integration/edge-runtime-streaming-error/pages/api/test.js @@ -1,6 +1,7 @@ export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } + export default function () { return new Response( new ReadableStream({ diff --git a/test/integration/edge-runtime-with-node.js-apis/pages/api/route.js b/test/integration/edge-runtime-with-node.js-apis/pages/api/route.js index 8c38026256157..910521218d8f6 100644 --- a/test/integration/edge-runtime-with-node.js-apis/pages/api/route.js +++ b/test/integration/edge-runtime-with-node.js-apis/pages/api/route.js @@ -5,4 +5,4 @@ export default async function handler(request) { return Response.json({ ok: true }) } -export const config = { runtime: 'experimental-edge' } +export const config = { runtime: 'edge' } diff --git a/test/integration/image-generation/app/pages/api/image.jsx b/test/integration/image-generation/app/pages/api/image.jsx index 518c3d654e00d..a15b0cc50f16d 100644 --- a/test/integration/image-generation/app/pages/api/image.jsx +++ b/test/integration/image-generation/app/pages/api/image.jsx @@ -5,5 +5,5 @@ export default async () => { } export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } diff --git a/test/integration/telemetry/pages/api/og.jsx b/test/integration/telemetry/pages/api/og.jsx index 518c3d654e00d..a15b0cc50f16d 100644 --- a/test/integration/telemetry/pages/api/og.jsx +++ b/test/integration/telemetry/pages/api/og.jsx @@ -5,5 +5,5 @@ export default async () => { } export const config = { - runtime: 'experimental-edge', + runtime: 'edge', } diff --git a/test/production/generate-middleware-source-maps/index.test.ts b/test/production/generate-middleware-source-maps/index.test.ts index 9e8ac97a80a69..b869e80e17bd3 100644 --- a/test/production/generate-middleware-source-maps/index.test.ts +++ b/test/production/generate-middleware-source-maps/index.test.ts @@ -13,7 +13,7 @@ describe('Middleware source maps', () => { export default function () { return
Hello, world!
} `, 'pages/api/edge.js': ` - export const config = { runtime: 'experimental-edge' }; + export const config = { runtime: 'edge' }; export default function (req) { return new Response("Hello from " + req.url); }