Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/next/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -967,5 +967,7 @@
"966": "Expected process.nextTick to reject invalid arguments",
"967": "The key \"%s\" under \"env\" in %sconfig is not allowed. https://nextjs.org/docs/messages/env-key-not-allowed",
"968": "Invariant: getNextConfigEdge must only be called in edge runtime",
"969": "Invariant: nextConfig couldn't be loaded"
"969": "Invariant: nextConfig couldn't be loaded",
"970": "process.env.NEXT_DEPLOYMENT_ID is missing but runtimeServerDeploymentId is enabled",
"971": "The NEXT_DEPLOYMENT_ID environment variable value \"%s\" does not match the provided deploymentId \"%s\" in the config."
}
39 changes: 28 additions & 11 deletions packages/next/src/build/define-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ interface SerializedDefineEnv {
* Serializes the DefineEnv config so that it can be inserted into the code by Webpack/Turbopack, JSON stringifies each value.
*/
function serializeDefineEnv(defineEnv: DefineEnv): SerializedDefineEnv {
const defineEnvStringified: SerializedDefineEnv = {}
for (const key in defineEnv) {
const value = defineEnv[key]
defineEnvStringified[key] = JSON.stringify(value)
}

const defineEnvStringified: SerializedDefineEnv = Object.fromEntries(
Object.entries(defineEnv).map(([key, value]) => [
key,
JSON.stringify(value),
])
)
return defineEnvStringified
}

Expand Down Expand Up @@ -167,9 +167,24 @@ export function getDefineEnv({
'process.env.__NEXT_CACHE_COMPONENTS': isCacheComponentsEnabled,
'process.env.__NEXT_USE_CACHE': isUseCacheEnabled,

'process.env.NEXT_DEPLOYMENT_ID': config.experimental?.useSkewCookie
? false
: config.deploymentId || false,
...(isClient
? {
// TODO use `globalThis.NEXT_DEPLOYMENT_ID` on client to still support accessing
// process.env.NEXT_DEPLOYMENT_ID in userland
'process.env.NEXT_DEPLOYMENT_ID': config.experimental?.useSkewCookie
? false
: config.deploymentId || false,
}
: config.experimental?.runtimeServerDeploymentId
? {
// Don't inline at all, keep process.env.NEXT_DEPLOYMENT_ID as is
}
: {
'process.env.NEXT_DEPLOYMENT_ID': config.experimental?.useSkewCookie
? false
: config.deploymentId || false,
}),

// Propagates the `__NEXT_EXPERIMENTAL_STATIC_SHELL_DEBUGGING` environment
// variable to the client.
'process.env.__NEXT_EXPERIMENTAL_STATIC_SHELL_DEBUGGING':
Expand Down Expand Up @@ -372,8 +387,10 @@ export function getDefineEnv({
for (const key in nextConfigEnv) {
serializedDefineEnv[key] = safeKey(key)
}
for (const key of ['process.env.NEXT_DEPLOYMENT_ID']) {
serializedDefineEnv[key] = safeKey(key)
if (!config.experimental.runtimeServerDeploymentId) {
for (const key of ['process.env.NEXT_DEPLOYMENT_ID']) {
serializedDefineEnv[key] = safeKey(key)
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -989,7 +989,7 @@ export default async function build(
// when using compile mode static env isn't inlined so we
// need to populate in normal runtime env
if (isCompileMode || isGenerateMode) {
populateStaticEnv(config)
populateStaticEnv(config, config.deploymentId)
}

const customRoutes: CustomRoutes = await nextBuildSpan
Expand Down
3 changes: 2 additions & 1 deletion packages/next/src/build/templates/app-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ export async function handler(
nextConfig,
parsedUrl,
interceptionRoutePatterns,
deploymentId,
} = prepareResult

const normalizedSrcPage = normalizeAppPath(srcPage)
Expand Down Expand Up @@ -559,7 +560,7 @@ export async function handler(
trailingSlash: nextConfig.trailingSlash,
images: nextConfig.images,
previewProps: prerenderManifest.preview,
deploymentId: nextConfig.deploymentId,
deploymentId: deploymentId,
enableTainting: nextConfig.experimental.taint,
htmlLimitedBots: nextConfig.htmlLimitedBots,
reactMaxHeadersLength: nextConfig.reactMaxHeadersLength,
Expand Down
3 changes: 2 additions & 1 deletion packages/next/src/build/templates/edge-ssr-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ async function requestHandler(
resolvedPathname,
interceptionRoutePatterns,
routerServerContext,
deploymentId,
} = prepareResult

// Initialize the cache handlers interface.
Expand Down Expand Up @@ -137,7 +138,7 @@ async function requestHandler(
trailingSlash: nextConfig.trailingSlash,
images: nextConfig.images,
previewProps: prerenderManifest.preview,
deploymentId: nextConfig.deploymentId,
deploymentId,
enableTainting: nextConfig.experimental.taint,
htmlLimitedBots: nextConfig.htmlLimitedBots,
reactMaxHeadersLength: nextConfig.reactMaxHeadersLength,
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/lib/inline-static-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function inlineStaticEnv({
config: NextConfigComplete
}) {
const nextConfigEnv = getNextConfigEnv(config)
const staticEnv = getStaticEnv(config)
const staticEnv = getStaticEnv(config, config.deploymentId)

const serverDir = path.join(distDir, 'server')
const serverChunks = await glob('**/*.{js,json,js.map}', {
Expand Down
12 changes: 8 additions & 4 deletions packages/next/src/lib/static-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,26 @@ export function getNextConfigEnv(
return defineEnv
}

export function getStaticEnv(config: NextConfigComplete | NextConfigRuntime) {
export function getStaticEnv(
config: NextConfigComplete | NextConfigRuntime,
deploymentId: string
) {
const staticEnv: Record<string, string | undefined> = {
...getNextPublicEnvironmentVariables(),
...getNextConfigEnv(config),
'process.env.NEXT_DEPLOYMENT_ID': config.deploymentId || '',
'process.env.NEXT_DEPLOYMENT_ID': deploymentId,
}
return staticEnv
}

export function populateStaticEnv(
config: NextConfigComplete | NextConfigRuntime
config: NextConfigComplete | NextConfigRuntime,
deploymentId: string
) {
// since inlining comes after static generation we need
// to ensure this value is assigned to process env so it
// can still be accessed
const staticEnv = getStaticEnv(config)
const staticEnv = getStaticEnv(config, deploymentId)
for (const key in staticEnv) {
const innerKey = key.split('.').pop() || ''
if (!process.env[innerKey]) {
Expand Down
21 changes: 19 additions & 2 deletions packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,24 @@ export default abstract class Server<
// TODO: should conf be normalized to prevent missing
// values from causing issues as this can be user provided
this.nextConfig = conf as NextConfigRuntime

let deploymentId
if (this.nextConfig.experimental.runtimeServerDeploymentId) {
if (!process.env.NEXT_DEPLOYMENT_ID) {
throw new Error(
'process.env.NEXT_DEPLOYMENT_ID is missing but runtimeServerDeploymentId is enabled'
)
}
deploymentId = process.env.NEXT_DEPLOYMENT_ID
} else {
let id = this.nextConfig.experimental.useSkewCookie
? ''
: this.nextConfig.deploymentId || ''

deploymentId = id
process.env.NEXT_DEPLOYMENT_ID = id
}

this.hostname = hostname
if (this.hostname) {
// we format the hostname so that it can be fetched
Expand Down Expand Up @@ -501,13 +519,12 @@ export default abstract class Server<
}

this.nextFontManifest = this.getNextFontManifest()
process.env.NEXT_DEPLOYMENT_ID = this.nextConfig.deploymentId || ''

this.renderOpts = {
dir: this.dir,
supportsDynamicResponse: true,
trailingSlash: this.nextConfig.trailingSlash,
deploymentId: this.nextConfig.deploymentId,
deploymentId: deploymentId,
poweredByHeader: this.nextConfig.poweredByHeader,
generateEtags,
previewProps: this.getPrerenderManifest().preview,
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/server/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ export const experimentalSchema = {
.optional(),
lockDistDir: z.boolean().optional(),
hideLogsAfterAbort: z.boolean().optional(),
runtimeServerDeploymentId: z.boolean().optional(),
}

export const configSchema: zod.ZodType<NextConfig> = z.lazy(() =>
Expand Down
18 changes: 15 additions & 3 deletions packages/next/src/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,14 @@ export interface ExperimentalConfig {
* @default false
*/
hideLogsAfterAbort?: boolean

/**
* Whether `process.env.NEXT_DEPLOYMENT_ID` is available at runtime in the server (and `next
* build` doesn't need to embed the deployment ID value into the build output).
*
* @default false
*/
runtimeServerDeploymentId?: boolean
}

export type ExportPathMap = {
Expand Down Expand Up @@ -1586,8 +1594,8 @@ export async function normalizeConfig(phase: string, config: any) {
// };
// };
export interface NextConfigRuntime {
// TODO remove in some cases
deploymentId: NextConfigComplete['deploymentId']
// Can be undefined, particularly when experimental.runtimeServerDeploymentId is true
deploymentId?: NextConfigComplete['deploymentId']

configFileName?: string
// Should only be included when using isExperimentalCompile
Expand Down Expand Up @@ -1652,6 +1660,7 @@ export interface NextConfigRuntime {
| 'proxyClientMaxBodySize'
| 'proxyTimeout'
| 'testProxy'
| 'runtimeServerDeploymentId'
> & {
// Pick on @internal fields generates invalid .d.ts files
/** @internal */
Expand Down Expand Up @@ -1706,14 +1715,17 @@ export function getNextConfigRuntime(
proxyClientMaxBodySize: ex.proxyClientMaxBodySize,
proxyTimeout: ex.proxyTimeout,
testProxy: ex.testProxy,
runtimeServerDeploymentId: ex.runtimeServerDeploymentId,

trustHostHeader: ex.trustHostHeader,
isExperimentalCompile: ex.isExperimentalCompile,
} satisfies Requiredish<NextConfigRuntime['experimental']>)
: {}

let runtimeConfig: Requiredish<NextConfigRuntime> = {
deploymentId: config.deploymentId,
deploymentId: config.experimental.runtimeServerDeploymentId
? ''
: config.deploymentId,

configFileName: undefined,
env: undefined,
Expand Down
19 changes: 18 additions & 1 deletion packages/next/src/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ function assignDefaultsAndValidate(
}

// ensure correct default is set for api-resolver revalidate handling
if (!result.experimental?.trustHostHeader && ciEnvironment.hasNextSupport) {
if (!result.experimental.trustHostHeader && ciEnvironment.hasNextSupport) {
result.experimental.trustHostHeader = true
}

Expand Down Expand Up @@ -916,6 +916,23 @@ function assignDefaultsAndValidate(
}
}

if (
result.experimental.runtimeServerDeploymentId == null &&
phase === PHASE_PRODUCTION_BUILD &&
ciEnvironment.hasNextSupport &&
process.env.NEXT_DEPLOYMENT_ID
) {
if (
result.deploymentId != null &&
result.deploymentId !== process.env.NEXT_DEPLOYMENT_ID
) {
throw new Error(
`The NEXT_DEPLOYMENT_ID environment variable value "${process.env.NEXT_DEPLOYMENT_ID}" does not match the provided deploymentId "${result.deploymentId}" in the config.`
)
}
result.experimental.runtimeServerDeploymentId = true
}

// only leverage deploymentId
if (process.env.NEXT_DEPLOYMENT_ID) {
result.deploymentId = process.env.NEXT_DEPLOYMENT_ID
Expand Down
7 changes: 2 additions & 5 deletions packages/next/src/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,6 @@ export default class NextNodeServer extends BaseServer<
if (this.renderOpts.nextScriptWorkers) {
process.env.__NEXT_SCRIPT_WORKERS = JSON.stringify(true)
}
process.env.NEXT_DEPLOYMENT_ID = this.nextConfig.experimental.useSkewCookie
? ''
: this.nextConfig.deploymentId || ''

if (!this.minimalMode) {
this.imageResponseCache = new ResponseCache(this.minimalMode)
Expand Down Expand Up @@ -356,7 +353,7 @@ export default class NextNodeServer extends BaseServer<
// when using compile mode static env isn't inlined so we
// need to populate in normal runtime env
if (this.renderOpts.isExperimentalCompile) {
populateStaticEnv(this.nextConfig)
populateStaticEnv(this.nextConfig, this.renderOpts.deploymentId || '')
}

const shouldRemoveUncaughtErrorAndRejectionListeners = Boolean(
Expand Down Expand Up @@ -729,7 +726,7 @@ export default class NextNodeServer extends BaseServer<
renderOpts as LoadedRenderOpts<PagesModule>,
{
buildId: this.buildId,
deploymentId: this.nextConfig.deploymentId,
deploymentId: this.renderOpts.deploymentId,
customServer: this.serverOptions.customServer || undefined,
},
{
Expand Down
Loading
Loading