Skip to content

Commit

Permalink
Add process env NEXT_RUNTIME (#36383)
Browse files Browse the repository at this point in the history
* To help determine the current running environment is in server nodejs / edge runtime
* Remove usage of `process.browser`
  • Loading branch information
huozhi committed Apr 26, 2022
1 parent 487f7ab commit d5e767b
Show file tree
Hide file tree
Showing 14 changed files with 77 additions and 30 deletions.
4 changes: 2 additions & 2 deletions errors/env-key-not-allowed.md
Expand Up @@ -2,8 +2,8 @@

#### Why This Error Occurred

Next.js configures internal variables for replacement itself. These start with `__` or `NODE_`, for this reason they are not allowed as values for `env` in `next.config.js`
Next.js configures internal variables for replacement itself. `NEXT_RUNTIME` along with variables starting with `__` or `NODE_` are currently internal, for this reason they are not allowed as values for `env` in `next.config.js`

#### Possible Ways to Fix It

Rename the specified key so that it does not start with `__` or `NODE_`.
Rename the specified key so that it does not start with `__` or `NODE_`, or pick other another name for `NEXT_RUNTIME`.
1 change: 1 addition & 0 deletions packages/next/bin/next.ts
Expand Up @@ -104,6 +104,7 @@ if (process.env.NODE_ENV) {
}

;(process.env as any).NODE_ENV = process.env.NODE_ENV || defaultEnv
;(process.env as any).NEXT_RUNTIME = 'nodejs'

// x-ref: https://github.com/vercel/next.js/pull/34688#issuecomment-1047994505
if (process.versions.pnp === '3') {
Expand Down
24 changes: 18 additions & 6 deletions packages/next/build/webpack-config.ts
Expand Up @@ -1354,11 +1354,7 @@ export default async function getBaseWebpackConfig(
{}
),
...Object.keys(config.env).reduce((acc, key) => {
if (/^(?:NODE_.+)|^(?:__.+)$/i.test(key)) {
throw new Error(
`The key "${key}" under "env" in ${config.configFileName} is not allowed. https://nextjs.org/docs/messages/env-key-not-allowed`
)
}
errorIfEnvConflicted(config, key)

return {
...acc,
Expand All @@ -1369,11 +1365,16 @@ export default async function getBaseWebpackConfig(
'process.env.NODE_ENV': JSON.stringify(
dev ? 'development' : 'production'
),
...(isServer && {
'process.env.NEXT_RUNTIME': JSON.stringify(
isEdgeRuntime ? 'edge' : 'nodejs'
),
}),
'process.env.__NEXT_NEW_LINK_BEHAVIOR': JSON.stringify(
config.experimental.newNextLinkBehavior
),
'process.env.__NEXT_CROSS_ORIGIN': JSON.stringify(crossOrigin),
'process.browser': JSON.stringify(targetWeb),
'process.browser': JSON.stringify(!isServer),
'process.env.__NEXT_TEST_MODE': JSON.stringify(
process.env.__NEXT_TEST_MODE
),
Expand Down Expand Up @@ -2182,3 +2183,14 @@ export default async function getBaseWebpackConfig(

return webpackConfig
}

function errorIfEnvConflicted(config: NextConfigComplete, key: string) {
const isPrivateKey = /^(?:NODE_.+)|^(?:__.+)$/i.test(key)
const hasNextRuntimeKey = key === 'NEXT_RUNTIME'

if (isPrivateKey || hasNextRuntimeKey) {
throw new Error(
`The key "${key}" under "env" in ${config.configFileName} is not allowed. https://nextjs.org/docs/messages/env-key-not-allowed`
)
}
}
6 changes: 3 additions & 3 deletions packages/next/lib/chalk.ts
@@ -1,9 +1,9 @@
let chalk: typeof import('next/dist/compiled/chalk')

if (!process.browser) {
chalk = require('next/dist/compiled/chalk')
} else {
if (process.env.NEXT_RUNTIME === 'edge') {
chalk = require('./web/chalk').default
} else {
chalk = require('next/dist/compiled/chalk')
}

export default chalk
2 changes: 1 addition & 1 deletion packages/next/pages/_document.tsx
Expand Up @@ -80,7 +80,7 @@ function getPreNextWorkerScripts(context: HtmlProps, props: OriginProps) {
const { assetPrefix, scriptLoader, crossOrigin, nextScriptWorkers } = context

// disable `nextScriptWorkers` in edge runtime
if (!nextScriptWorkers || process.browser) return null
if (!nextScriptWorkers || process.env.NEXT_RUNTIME === 'edge') return null

try {
let {
Expand Down
9 changes: 6 additions & 3 deletions packages/next/server/base-server.ts
Expand Up @@ -1155,7 +1155,7 @@ export default abstract class Server {
(isServerComponent &&
!hasServerProps &&
!hasGetInitialProps &&
!process.browser)
process.env.NEXT_RUNTIME !== 'edge')

// Toggle whether or not this is a Data request
const isDataReq =
Expand Down Expand Up @@ -1240,7 +1240,7 @@ export default abstract class Server {

if (hasServerProps || isSSG) {
// For the edge runtime, we don't support preview mode in SSG.
if (!process.browser) {
if (process.env.NEXT_RUNTIME !== 'edge') {
const { tryGetPreviewData } =
require('./api-utils/node') as typeof import('./api-utils/node')
previewData = tryGetPreviewData(req, res, this.renderOpts.previewProps)
Expand Down Expand Up @@ -1758,7 +1758,10 @@ export default abstract class Server {
)

if (!isWrappedError) {
if ((this.minimalMode && !process.browser) || this.renderOpts.dev) {
if (
(this.minimalMode && process.env.NEXT_RUNTIME !== 'edge') ||
this.renderOpts.dev
) {
if (isError(err)) err.page = page
throw err
}
Expand Down
30 changes: 20 additions & 10 deletions packages/next/server/render.tsx
Expand Up @@ -89,7 +89,7 @@ let postProcess: typeof import('../shared/lib/post-process').default

const DOCTYPE = '<!DOCTYPE html>'

if (!process.browser) {
if (process.env.NEXT_RUNTIME !== 'edge') {
require('./node-polyfill-web-streams')
optimizeAmp = require('./optimize-amp').default
getFontDefinitionFromManifest =
Expand Down Expand Up @@ -518,7 +518,9 @@ export async function renderToHTML(
Uint8Array,
Uint8Array
> | null =
isServerComponent && !process.browser ? new TransformStream() : null
isServerComponent && process.env.NEXT_RUNTIME !== 'edge'
? new TransformStream()
: null

if (isServerComponent) {
serverComponentsInlinedTransformStream = new TransformStream()
Expand Down Expand Up @@ -700,7 +702,11 @@ export async function renderToHTML(
let isPreview
let previewData: PreviewData

if ((isSSG || getServerSideProps) && !isFallback && !process.browser) {
if (
(isSSG || getServerSideProps) &&
!isFallback &&
process.env.NEXT_RUNTIME !== 'edge'
) {
// Reads of this are cached on the `req` object, so this should resolve
// instantly. There's no need to pass this data down from a previous
// invoke, where we'd have to consider server & serverless.
Expand Down Expand Up @@ -775,7 +781,7 @@ export async function renderToHTML(
}

// Disable AMP under the web environment
const inAmpMode = !process.browser && isInAmpMode(ampState)
const inAmpMode = process.env.NEXT_RUNTIME !== 'edge' && isInAmpMode(ampState)

const reactLoadableModules: string[] = []

Expand Down Expand Up @@ -1313,7 +1319,7 @@ export async function renderToHTML(
| typeof Document
| undefined

if (process.browser && Document.getInitialProps) {
if (process.env.NEXT_RUNTIME === 'edge' && Document.getInitialProps) {
// In the Edge runtime, `Document.getInitialProps` isn't supported.
// We throw an error here if it's customized.
if (!builtinDocument) {
Expand All @@ -1323,7 +1329,10 @@ export async function renderToHTML(
}
}

if ((isServerComponent || process.browser) && Document.getInitialProps) {
if (
(isServerComponent || process.env.NEXT_RUNTIME === 'edge') &&
Document.getInitialProps
) {
if (builtinDocument) {
Document = builtinDocument
} else {
Expand Down Expand Up @@ -1541,7 +1550,7 @@ export async function renderToHTML(

const hasDocumentGetInitialProps = !(
isServerComponent ||
process.browser ||
process.env.NEXT_RUNTIME === 'edge' ||
!Document.getInitialProps
)

Expand All @@ -1560,7 +1569,7 @@ export async function renderToHTML(

const { docProps } = (documentInitialPropsRes as any) || {}
const documentElement = () => {
if (isServerComponent || process.browser) {
if (isServerComponent || process.env.NEXT_RUNTIME === 'edge') {
return (Document as any)()
}

Expand Down Expand Up @@ -1751,7 +1760,8 @@ export async function renderToHTML(
return html
}
: null,
!process.browser && process.env.__NEXT_OPTIMIZE_FONTS
process.env.NEXT_RUNTIME !== 'edge' &&
process.env.__NEXT_OPTIMIZE_FONTS
? async (html: string) => {
return await postProcess(
html,
Expand All @@ -1762,7 +1772,7 @@ export async function renderToHTML(
)
}
: null,
!process.browser && renderOpts.optimizeCss
process.env.NEXT_RUNTIME !== 'edge' && renderOpts.optimizeCss
? async (html: string) => {
// eslint-disable-next-line import/no-extraneous-dependencies
const Critters = require('critters')
Expand Down
4 changes: 3 additions & 1 deletion packages/next/server/web/sandbox/context.ts
Expand Up @@ -265,7 +265,9 @@ function buildEnvironmentVariablesFrom(
keys: string[]
): Record<string, string | undefined> {
const pairs = keys.map((key) => [key, process.env[key]])
return Object.fromEntries(pairs)
const env = Object.fromEntries(pairs)
env.NEXT_RUNTIME = 'edge'
return env
}

async function loadWasm(
Expand Down
7 changes: 4 additions & 3 deletions packages/next/shared/lib/isomorphic/path.ts
@@ -1,5 +1,6 @@
const path = process.browser
? require('next/dist/compiled/path-browserify')
: require('path')
const path =
process.env.NEXT_RUNTIME === 'edge'
? require('next/dist/compiled/path-browserify')
: require('path')

export default path
2 changes: 2 additions & 0 deletions test/integration/middleware/core/test/index.test.js
Expand Up @@ -119,6 +119,7 @@ describe('Middleware base tests', () => {
context.app = await launchApp(context.appDir, context.appPort, {
env: {
MIDDLEWARE_TEST: 'asdf',
NEXT_RUNTIME: 'edge',
},
})
})
Expand All @@ -130,6 +131,7 @@ describe('Middleware base tests', () => {
process: {
env: {
MIDDLEWARE_TEST: 'asdf',
NEXT_RUNTIME: 'edge',
},
// it's poflyfilled since there is the "process" module
// as a devDepencies of the next package
Expand Down
5 changes: 5 additions & 0 deletions test/integration/production-config/next.config.js
Expand Up @@ -15,6 +15,11 @@ module.exports = {
SOME__ENV__VAR: '123',
}
: {}),
...(process.env.ENABLE_ENV_NEXT_PRESERVED
? {
NEXT_RUNTIME: 'nodejs',
}
: {}),
},
onDemandEntries: {
// Make sure entries are not getting disposed.
Expand Down
10 changes: 10 additions & 0 deletions test/integration/production-config/test/index.test.js
Expand Up @@ -56,6 +56,16 @@ describe('Production Config Usage', () => {
expect(result.stderr).toMatch(/The key "NODE_ENV" under/)
})

it('should fail with NEXT_RUNTIME in env key', async () => {
const result = await runNextCommand(['build', appDir], {
env: { ENABLE_ENV_NEXT_PRESERVED: true },
stdout: true,
stderr: true,
})

expect(result.stderr).toMatch(/The key "NEXT_RUNTIME" under/)
})

it('should allow __ within env key', async () => {
const result = await runNextCommand(['build', appDir], {
env: { ENABLE_ENV_WITH_UNDERSCORES: true },
Expand Down
@@ -1,5 +1,5 @@
export default function () {
return process.version
return process.env.NEXT_RUNTIME === 'nodejs'
? `Runtime: Node.js ${process.version}`
: 'Runtime: Edge/Browser'
}
Expand Down
Expand Up @@ -44,6 +44,7 @@ describe('middleware environment variables in node server reflect the usage infe
rest: {
CAN_BE_INFERRED: 'can-be-inferred',
X_CUSTOM_HEADER: 'x-custom-header',
NEXT_RUNTIME: 'edge',
},
})
})
Expand Down

0 comments on commit d5e767b

Please sign in to comment.