Skip to content

Commit

Permalink
feat: enables 'edge' as a possible runtime for API routes, prevents r…
Browse files Browse the repository at this point in the history
…endering pages on stable edge runtime
  • Loading branch information
feugy committed Dec 13, 2022
1 parent 04c2509 commit 493fb3d
Show file tree
Hide file tree
Showing 58 changed files with 287 additions and 126 deletions.
2 changes: 1 addition & 1 deletion docs/advanced-features/react-18/switchable-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,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!')
Expand Down
2 changes: 1 addition & 1 deletion docs/api-reference/edge-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions docs/api-routes/edge-api-routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!')
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion errors/edge-dynamic-code-evaluation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion errors/middleware-upgrade-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion examples/with-webassembly/pages/api/edge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' }
2 changes: 1 addition & 1 deletion packages/next/build/analysis/extract-const-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 34 additions & 18 deletions packages/next/build/analysis/get-page-static-info.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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<string, boolean>()
Expand Down Expand Up @@ -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') {
Expand All @@ -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(
Expand Down
12 changes: 6 additions & 6 deletions packages/next/build/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -257,8 +257,8 @@ export async function runDependingOnPageType<T>(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
}
Expand All @@ -279,7 +279,7 @@ export async function runDependingOnPageType<T>(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
}
Expand Down
8 changes: 4 additions & 4 deletions packages/next/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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}`
Expand Down
6 changes: 3 additions & 3 deletions packages/next/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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 {
Expand Down Expand Up @@ -423,7 +423,7 @@ export async function printTreeView(
? '○'
: pageInfo?.isSsg
? '●'
: pageInfo?.runtime === SERVER_RUNTIME.edge
: isEdgeRuntime(pageInfo?.runtime)
? 'ℇ'
: 'λ'

Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions packages/next/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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`.'
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export function getRender({
pagesType,
extendRenderOpts: {
buildId,
runtime: SERVER_RUNTIME.edge,
runtime: SERVER_RUNTIME.experimentalEdge,
supportsDynamicHTML: true,
disableOptimizedLoading: true,
serverComponentManifest,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -88,7 +88,7 @@ const nextServerlessLoader: webpack.LoaderDefinitionFunction = function () {
`
: 'const runtimeConfig = {}'

if (page.match(API_ROUTE)) {
if (isAPIRoute(page)) {
return `
${envLoading}
${runtimeConfigImports}
Expand Down
6 changes: 5 additions & 1 deletion packages/next/build/webpack/plugins/flight-types-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'"
: ''
}
}
// =============
Expand Down
Loading

0 comments on commit 493fb3d

Please sign in to comment.