Skip to content

Commit ecab0b9

Browse files
committed
fix: conditionally enable use cache handler based on next version
1 parent 77d64c9 commit ecab0b9

File tree

5 files changed

+57
-32
lines changed

5 files changed

+57
-32
lines changed

src/build/content/server.ts

+15-14
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ import { join as posixJoin, sep as posixSep } from 'node:path/posix'
1616
import { trace } from '@opentelemetry/api'
1717
import { wrapTracer } from '@opentelemetry/api/experimental'
1818
import glob from 'fast-glob'
19+
import type { NextConfigComplete } from 'next/dist/server/config-shared.js'
1920
import { prerelease, lt as semverLowerThan, lte as semverLowerThanOrEqual } from 'semver'
2021

21-
import { RUN_CONFIG } from '../../run/constants.js'
22-
import { PluginContext } from '../plugin-context.js'
22+
import type { RunConfig } from '../../run/config.js'
23+
import { RUN_CONFIG_FILE } from '../../run/constants.js'
24+
import type { PluginContext, RequiredServerFilesManifest } from '../plugin-context.js'
2325

2426
const tracer = wrapTracer(trace.getTracer('Next runtime'))
2527

@@ -32,8 +34,8 @@ function isError(error: unknown): error is NodeJS.ErrnoException {
3234
/**
3335
* Copy App/Pages Router Javascript needed by the server handler
3436
*/
35-
export const copyNextServerCode = async (ctx: PluginContext): Promise<void> => {
36-
await tracer.withActiveSpan('copyNextServerCode', async () => {
37+
export const copyNextServerCode = async (ctx: PluginContext): Promise<NextConfigComplete> => {
38+
return await tracer.withActiveSpan('copyNextServerCode', async () => {
3739
// update the dist directory inside the required-server-files.json to work with
3840
// nx monorepos and other setups where the dist directory is modified
3941
const reqServerFilesPath = join(
@@ -54,7 +56,9 @@ export const copyNextServerCode = async (ctx: PluginContext): Promise<void> => {
5456
throw error
5557
}
5658
}
57-
const reqServerFiles = JSON.parse(await readFile(reqServerFilesPath, 'utf-8'))
59+
const reqServerFiles = JSON.parse(
60+
await readFile(reqServerFilesPath, 'utf-8'),
61+
) as RequiredServerFilesManifest
5862

5963
// if the resolved dist folder does not match the distDir of the required-server-files.json
6064
// this means the path got altered by a plugin like nx and contained ../../ parts so we have to reset it
@@ -70,13 +74,6 @@ export const copyNextServerCode = async (ctx: PluginContext): Promise<void> => {
7074

7175
// ensure the directory exists before writing to it
7276
await mkdir(ctx.serverHandlerDir, { recursive: true })
73-
// write our run-config.json to the root dir so that we can easily get the runtime config of the required-server-files.json
74-
// without the need to know about the monorepo or distDir configuration upfront.
75-
await writeFile(
76-
join(ctx.serverHandlerDir, RUN_CONFIG),
77-
JSON.stringify(reqServerFiles.config),
78-
'utf-8',
79-
)
8077

8178
const srcDir = join(ctx.standaloneDir, ctx.nextDistDir)
8279
// if the distDir got resolved and altered use the nextDistDir instead
@@ -114,6 +111,8 @@ export const copyNextServerCode = async (ctx: PluginContext): Promise<void> => {
114111
await cp(srcPath, destPath, { recursive: true, force: true })
115112
}),
116113
)
114+
115+
return reqServerFiles.config
117116
})
118117
}
119118

@@ -336,9 +335,11 @@ const replaceMiddlewareManifest = async (sourcePath: string, destPath: string) =
336335
}
337336

338337
export const verifyHandlerDirStructure = async (ctx: PluginContext) => {
339-
const runConfig = JSON.parse(await readFile(join(ctx.serverHandlerDir, RUN_CONFIG), 'utf-8'))
338+
const { nextConfig } = JSON.parse(
339+
await readFile(join(ctx.serverHandlerDir, RUN_CONFIG_FILE), 'utf-8'),
340+
) as RunConfig
340341

341-
const expectedBuildIDPath = join(ctx.serverHandlerDir, runConfig.distDir, 'BUILD_ID')
342+
const expectedBuildIDPath = join(ctx.serverHandlerDir, nextConfig.distDir, 'BUILD_ID')
342343
if (!existsSync(expectedBuildIDPath)) {
343344
ctx.failBuild(
344345
`Failed creating server handler. BUILD_ID file not found at expected location "${expectedBuildIDPath}".`,

src/build/functions/server.ts

+26-2
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@ import { join as posixJoin } from 'node:path/posix'
55
import { trace } from '@opentelemetry/api'
66
import { wrapTracer } from '@opentelemetry/api/experimental'
77
import { glob } from 'fast-glob'
8+
import type { NextConfigComplete } from 'next/dist/server/config-shared.js'
9+
import { satisfies } from 'semver'
810

11+
import type { RunConfig } from '../../run/config.js'
12+
import { RUN_CONFIG_FILE } from '../../run/constants.js'
913
import {
1014
copyNextDependencies,
1115
copyNextServerCode,
1216
verifyHandlerDirStructure,
1317
} from '../content/server.js'
14-
import { PluginContext, SERVER_HANDLER_NAME } from '../plugin-context.js'
18+
import { type PluginContext, SERVER_HANDLER_NAME } from '../plugin-context.js'
1519

1620
const tracer = wrapTracer(trace.getTracer('Next runtime'))
1721

@@ -129,6 +133,25 @@ const writeHandlerFile = async (ctx: PluginContext) => {
129133
await writeFile(join(ctx.serverHandlerRootDir, `${SERVER_HANDLER_NAME}.mjs`), handler)
130134
}
131135

136+
const writeRunConfig = async (ctx: PluginContext, standaloneNextConfig: NextConfigComplete) => {
137+
// write our run-config.json to the root dir so that we can easily get the runtime config of the required-server-files.json
138+
// without the need to know about the monorepo or distDir configuration upfront.
139+
await writeFile(
140+
join(ctx.serverHandlerDir, RUN_CONFIG_FILE),
141+
JSON.stringify({
142+
nextConfig: standaloneNextConfig,
143+
// only enable setting up 'use cache' handler when Next.js supports CacheHandlerV2 as we don't have V1 compatible implementation
144+
// see https://github.com/vercel/next.js/pull/76687 first released in v15.3.0-canary.13
145+
enableUseCacheHandler: ctx.nextVersion
146+
? satisfies(ctx.nextVersion, '>=15.3.0-canary.13', {
147+
includePrerelease: true,
148+
})
149+
: false,
150+
} satisfies RunConfig),
151+
'utf-8',
152+
)
153+
}
154+
132155
export const clearStaleServerHandlers = async (ctx: PluginContext) => {
133156
await rm(ctx.serverFunctionsDir, { recursive: true, force: true })
134157
}
@@ -140,11 +163,12 @@ export const createServerHandler = async (ctx: PluginContext) => {
140163
await tracer.withActiveSpan('createServerHandler', async () => {
141164
await mkdir(join(ctx.serverHandlerRuntimeModulesDir), { recursive: true })
142165

143-
await copyNextServerCode(ctx)
166+
const standaloneNextConfig = await copyNextServerCode(ctx)
144167
await copyNextDependencies(ctx)
145168
await copyHandlerDependencies(ctx)
146169
await writeHandlerManifest(ctx)
147170
await writeHandlerFile(ctx)
171+
await writeRunConfig(ctx, standaloneNextConfig)
148172

149173
await verifyHandlerDirStructure(ctx)
150174
})

src/run/config.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@ import { join, resolve } from 'node:path'
44

55
import type { NextConfigComplete } from 'next/dist/server/config-shared.js'
66

7-
import { PLUGIN_DIR, RUN_CONFIG } from './constants.js'
7+
import { PLUGIN_DIR, RUN_CONFIG_FILE } from './constants.js'
88
import { setInMemoryCacheMaxSizeFromNextConfig } from './storage/storage.cjs'
99

10+
export type RunConfig = {
11+
nextConfig: NextConfigComplete
12+
enableUseCacheHandler: boolean
13+
}
14+
1015
/**
1116
* Get Next.js config from the build output
1217
*/
1318
export const getRunConfig = async () => {
14-
return JSON.parse(await readFile(resolve(PLUGIN_DIR, RUN_CONFIG), 'utf-8'))
19+
return JSON.parse(await readFile(resolve(PLUGIN_DIR, RUN_CONFIG_FILE), 'utf-8')) as RunConfig
1520
}
1621

1722
type NextConfigForMultipleVersions = NextConfigComplete & {

src/run/constants.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ import { fileURLToPath } from 'node:url'
44
export const MODULE_DIR = fileURLToPath(new URL('.', import.meta.url))
55
export const PLUGIN_DIR = resolve(`${MODULE_DIR}../../..`)
66
// a file where we store the required-server-files config object in to access during runtime
7-
export const RUN_CONFIG = 'run-config.json'
7+
export const RUN_CONFIG_FILE = 'run-config.json'

src/run/handlers/server.ts

+8-13
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import type { OutgoingHttpHeaders } from 'http'
33
import { ComputeJsOutgoingMessage, toComputeResponse, toReqRes } from '@fastly/http-compute-js'
44
import type { Context } from '@netlify/functions'
55
import { Span } from '@opentelemetry/api'
6-
import type { NextConfigComplete } from 'next/dist/server/config-shared.js'
76
import type { WorkerRequestHandler } from 'next/dist/server/lib/types.js'
87

8+
import { getRunConfig, setRunConfig } from '../config.js'
99
import {
1010
adjustDateHeader,
1111
setCacheControlHeaders,
@@ -20,20 +20,20 @@ import { getLogger, type RequestContext } from './request-context.cjs'
2020
import { getTracer, recordWarning } from './tracer.cjs'
2121
import { configureUseCacheHandlers } from './use-cache-handler.js'
2222
import { setupWaitUntil } from './wait-until.cjs'
23-
2423
// make use of global fetch before Next.js applies any patching
2524
setFetchBeforeNextPatchedIt(globalThis.fetch)
26-
// configure some globals that Next.js make use of before we start importing any Next.js code
25+
// configure globals that Next.js make use of before we start importing any Next.js code
2726
// as some globals are consumed at import time
28-
// TODO: only call this if Next.js version is using CacheHandlerV2 as we don't have V1 compatible implementation
29-
// see https://github.com/vercel/next.js/pull/76687
30-
// first released in v15.3.0-canary.13 so we should not run tests on older next versions
31-
configureUseCacheHandlers()
27+
const { nextConfig, enableUseCacheHandler } = await getRunConfig()
28+
if (enableUseCacheHandler) {
29+
configureUseCacheHandlers()
30+
}
31+
setRunConfig(nextConfig)
3232
setupWaitUntil()
3333

3434
const nextImportPromise = import('../next.cjs')
3535

36-
let nextHandler: WorkerRequestHandler, nextConfig: NextConfigComplete
36+
let nextHandler: WorkerRequestHandler
3737

3838
/**
3939
* When Next.js proxies requests externally, it writes the response back as-is.
@@ -68,11 +68,6 @@ export default async (
6868

6969
if (!nextHandler) {
7070
await tracer.withActiveSpan('initialize next server', async () => {
71-
// set the server config
72-
const { getRunConfig, setRunConfig } = await import('../config.js')
73-
nextConfig = await getRunConfig()
74-
setRunConfig(nextConfig)
75-
7671
const { getMockedRequestHandler } = await nextImportPromise
7772
const url = new URL(request.url)
7873

0 commit comments

Comments
 (0)