Skip to content

Commit 7dd77fe

Browse files
authored
Update build-complete for adapter API (#82452)
This adds some additional API surface for `build-complete` adapter hook. Wiring up prerenders and additional configs.
1 parent 46709a2 commit 7dd77fe

File tree

25 files changed

+718
-69
lines changed

25 files changed

+718
-69
lines changed

packages/next/errors.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -774,5 +774,6 @@
774774
"773": "Missing workStore in createPrerenderParamsForClientSegment",
775775
"774": "Route %s used %s outside of a Server Component. This is not allowed.",
776776
"775": "Node.js instrumentation extensions should not be loaded in the Edge runtime.",
777-
"776": "`unstable_isUnrecognizedActionError` can only be used on the client."
777+
"776": "`unstable_isUnrecognizedActionError` can only be used on the client.",
778+
"777": "Invariant: failed to find source route %s for prerender %s"
778779
}

packages/next/src/build/adapter/build-complete.ts

Lines changed: 204 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@ import * as Log from '../output/log'
66
import globOriginal from 'next/dist/compiled/glob'
77
import { interopDefault } from '../../lib/interop-default'
88
import type { AdapterOutputs, NextAdapter } from '../../server/config-shared'
9-
import {
10-
RouteType,
11-
type FunctionsConfigManifest,
12-
type PrerenderManifest,
13-
type RoutesManifest,
9+
import type {
10+
FunctionsConfigManifest,
11+
PrerenderManifest,
12+
RoutesManifest,
1413
} from '..'
1514
import type {
1615
EdgeFunctionDefinition,
@@ -19,6 +18,8 @@ import type {
1918
import { isMiddlewareFilename } from '../utils'
2019
import { normalizePagePath } from '../../shared/lib/page-path/normalize-page-path'
2120
import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths'
21+
import { AdapterOutputType } from '../../shared/lib/constants'
22+
import { RenderingMode } from '../rendering-mode'
2223

2324
const glob = promisify(globOriginal)
2425

@@ -33,8 +34,9 @@ export async function handleBuildComplete({
3334
hasInstrumentationHook,
3435
requiredServerFiles,
3536
routesManifest,
36-
// prerenderManifest,
37+
prerenderManifest,
3738
middlewareManifest,
39+
functionsConfigManifest,
3840
}: {
3941
dir: string
4042
distDir: string
@@ -68,7 +70,7 @@ export async function handleBuildComplete({
6870
const pathname = path.posix.join('/_next/static', file)
6971
const filePath = path.join(distDir, 'static', file)
7072
outputs.push({
71-
type: RouteType.STATIC_FILE,
73+
type: AdapterOutputType.STATIC_FILE,
7274
id: path.join('static', file),
7375
pathname,
7476
filePath,
@@ -126,19 +128,19 @@ export async function handleBuildComplete({
126128
page: EdgeFunctionDefinition,
127129
isMiddleware: boolean = false
128130
) {
129-
let type = RouteType.PAGES
131+
let type = AdapterOutputType.PAGES
130132
const isAppPrefix = page.page.startsWith('app/')
131133
const isAppPage = isAppPrefix && page.page.endsWith('/page')
132134
const isAppRoute = isAppPrefix && page.page.endsWith('/route')
133135

134136
if (isMiddleware) {
135-
type = RouteType.MIDDLEWARE
137+
type = AdapterOutputType.MIDDLEWARE
136138
} else if (isAppPage) {
137-
type = RouteType.APP_PAGE
139+
type = AdapterOutputType.APP_PAGE
138140
} else if (isAppRoute) {
139-
type = RouteType.APP_ROUTE
141+
type = AdapterOutputType.APP_ROUTE
140142
} else if (page.page.startsWith('/api')) {
141-
type = RouteType.PAGES_API
143+
type = AdapterOutputType.PAGES_API
142144
}
143145

144146
const output: AdapterOutputs[0] = {
@@ -155,6 +157,12 @@ export async function handleBuildComplete({
155157
),
156158
assets: {},
157159
type,
160+
config:
161+
type === AdapterOutputType.MIDDLEWARE
162+
? {
163+
matchers: page.matchers,
164+
}
165+
: {},
158166
}
159167

160168
function handleFile(file: string) {
@@ -188,6 +196,7 @@ export async function handleBuildComplete({
188196
for (const page of Object.values(middlewareManifest.functions)) {
189197
edgeFunctionHandlers.push(handleEdgeFunction(page))
190198
}
199+
const pageOutputMap: Record<string, AdapterOutputs[0]> = {}
191200

192201
for (const page of pageKeys) {
193202
if (middlewareManifest.functions.hasOwnProperty(page)) {
@@ -204,35 +213,50 @@ export async function handleBuildComplete({
204213
const pageTraceFile = `${pageFile}.nft.json`
205214
const assets = await handleTraceFiles(pageTraceFile).catch((err) => {
206215
if (err.code !== 'ENOENT' || (page !== '/404' && page !== '/500')) {
207-
Log.warn(`Failed to copy traced files for ${pageFile}`, err)
216+
Log.warn(`Failed to locate traced assets for ${pageFile}`, err)
208217
}
209218
return {} as Record<string, string>
210219
})
220+
const functionConfig = functionsConfigManifest.functions[route] || {}
211221

212-
outputs.push({
222+
const output: AdapterOutputs[0] = {
213223
id: route,
214-
type: page.startsWith('/api') ? RouteType.PAGES_API : RouteType.PAGES,
224+
type: page.startsWith('/api')
225+
? AdapterOutputType.PAGES_API
226+
: AdapterOutputType.PAGES,
215227
filePath: pageTraceFile.replace(/\.nft\.json$/, ''),
216228
pathname: route,
217229
assets,
218230
runtime: 'nodejs',
219-
})
231+
config: {
232+
maxDuration: functionConfig.maxDuration,
233+
preferredRegion: functionConfig.regions,
234+
},
235+
}
236+
pageOutputMap[page] = output
237+
outputs.push(output)
220238
}
221239

222240
if (hasNodeMiddleware) {
223241
const middlewareFile = path.join(distDir, 'server', 'middleware.js')
224242
const middlewareTrace = `${middlewareFile}.nft.json`
225243
const assets = await handleTraceFiles(middlewareTrace)
244+
const functionConfig =
245+
functionsConfigManifest.functions['/_middleware'] || {}
226246

227247
outputs.push({
228248
pathname: '/_middleware',
229249
id: '/_middleware',
230250
assets,
231-
type: RouteType.MIDDLEWARE,
251+
type: AdapterOutputType.MIDDLEWARE,
232252
runtime: 'nodejs',
233253
filePath: middlewareFile,
254+
config: {
255+
matchers: functionConfig.matchers,
256+
},
234257
})
235258
}
259+
const appOutputMap: Record<string, AdapterOutputs[0]> = {}
236260

237261
if (appPageKeys) {
238262
for (const page of appPageKeys) {
@@ -246,21 +270,180 @@ export async function handleBuildComplete({
246270
Log.warn(`Failed to copy traced files for ${pageFile}`, err)
247271
return {} as Record<string, string>
248272
})
273+
const functionConfig =
274+
functionsConfigManifest.functions[normalizedPage] || {}
249275

250-
outputs.push({
276+
const output: AdapterOutputs[0] = {
251277
pathname: normalizedPage,
252278
id: normalizedPage,
253279
assets,
254280
type: page.endsWith('/route')
255-
? RouteType.APP_ROUTE
256-
: RouteType.APP_PAGE,
281+
? AdapterOutputType.APP_ROUTE
282+
: AdapterOutputType.APP_PAGE,
257283
runtime: 'nodejs',
258284
filePath: pageFile,
285+
config: {
286+
maxDuration: functionConfig.maxDuration,
287+
preferredRegion: functionConfig.regions,
288+
},
289+
}
290+
appOutputMap[normalizedPage] = output
291+
outputs.push(output)
292+
}
293+
}
294+
295+
const getParentOutput = (srcRoute: string, childRoute: string) => {
296+
const parentOutput = pageOutputMap[srcRoute] || appOutputMap[srcRoute]
297+
298+
if (!parentOutput) {
299+
console.error({
300+
appOutputs: Object.keys(appOutputMap),
301+
pageOutputs: Object.keys(pageOutputMap),
302+
})
303+
throw new Error(
304+
`Invariant: failed to find source route ${srcRoute} for prerender ${childRoute}`
305+
)
306+
}
307+
return parentOutput
308+
}
309+
310+
for (const route in prerenderManifest.routes) {
311+
const {
312+
initialExpireSeconds: initialExpiration,
313+
initialRevalidateSeconds: initialRevalidate,
314+
initialHeaders,
315+
initialStatus,
316+
prefetchDataRoute,
317+
dataRoute,
318+
renderingMode,
319+
} = prerenderManifest.routes[route]
320+
321+
const srcRoute = prerenderManifest.routes[route].srcRoute || route
322+
const isAppPage = dataRoute?.endsWith('.rsc')
323+
324+
const filePath = path.join(
325+
distDir,
326+
'server',
327+
isAppPage ? 'app' : 'pages',
328+
`${route}.html`
329+
)
330+
const initialOutput = {
331+
id: route,
332+
type: AdapterOutputType.PRERENDER,
333+
pathname: route,
334+
parentOutputId: getParentOutput(srcRoute, route).id,
335+
fallback: {
336+
filePath,
337+
initialStatus,
338+
initialHeaders,
339+
initialExpiration,
340+
initialRevalidate,
341+
},
342+
config: {
343+
renderingMode,
344+
},
345+
}
346+
outputs.push(initialOutput)
347+
348+
if (dataRoute) {
349+
let dataFilePath = path.join(
350+
distDir,
351+
'server',
352+
'pages',
353+
`${route}.json`
354+
)
355+
356+
if (isAppPage) {
357+
// When experimental PPR is enabled, we expect that the data
358+
// that should be served as a part of the prerender should
359+
// be from the prefetch data route. If this isn't enabled
360+
// for ppr, the only way to get the data is from the data
361+
// route.
362+
dataFilePath = path.join(
363+
distDir,
364+
'server',
365+
'app',
366+
prefetchDataRoute &&
367+
renderingMode === RenderingMode.PARTIALLY_STATIC
368+
? prefetchDataRoute
369+
: dataRoute
370+
)
371+
}
372+
373+
outputs.push({
374+
...initialOutput,
375+
id: dataRoute,
376+
pathname: dataRoute,
377+
fallback: {
378+
...initialOutput.fallback,
379+
filePath: dataFilePath,
380+
},
259381
})
260382
}
261383
}
262384

263-
// TODO: prerender assets
385+
for (const dynamicRoute in prerenderManifest.dynamicRoutes) {
386+
const {
387+
fallback,
388+
fallbackExpire,
389+
fallbackRevalidate,
390+
fallbackHeaders,
391+
fallbackStatus,
392+
dataRoute,
393+
} = prerenderManifest.dynamicRoutes[dynamicRoute]
394+
395+
const isAppPage = dataRoute?.endsWith('.rsc')
396+
397+
const initialOutput = {
398+
id: dynamicRoute,
399+
type: AdapterOutputType.PRERENDER,
400+
pathname: dynamicRoute,
401+
parentOutputId: getParentOutput(dynamicRoute, dynamicRoute).id,
402+
fallback:
403+
typeof fallback === 'string'
404+
? {
405+
filePath: path.join(
406+
distDir,
407+
'server',
408+
isAppPage ? 'app' : 'pages',
409+
fallback
410+
),
411+
initialStatus: fallbackStatus,
412+
initialHeaders: fallbackHeaders,
413+
initialExpiration: fallbackExpire,
414+
initialRevalidate: fallbackRevalidate,
415+
}
416+
: undefined,
417+
}
418+
outputs.push(initialOutput)
419+
420+
if (dataRoute) {
421+
outputs.push({
422+
...initialOutput,
423+
id: dataRoute,
424+
pathname: dataRoute,
425+
})
426+
}
427+
}
428+
429+
// TODO: should these be normal outputs or meta on associated routes?
430+
// for (const route of prerenderManifest.notFoundRoutes) {
431+
// // The fallback here is the 404 page if statically generated
432+
// // if it is not then the fallback is empty and it is generated
433+
// // at runtime
434+
435+
// outputs.push({
436+
// id: route,
437+
// type: OutputType.PRERENDER,
438+
// pathname: route,
439+
// runtime: 'nodejs',
440+
// fallback: {
441+
// filePath: '',
442+
// initialStatus: 404,
443+
// initialHeaders: {},
444+
// },
445+
// })
446+
// }
264447

265448
await adapterMod.onBuildComplete({
266449
routes: {

packages/next/src/build/index.ts

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -372,39 +372,6 @@ type ManifestBuiltRoute = {
372372
regex: string
373373
}
374374

375-
export enum RouteType {
376-
/**
377-
* `PAGES` represents all the React pages that are under `pages/`.
378-
*/
379-
PAGES = 'PAGES',
380-
/**
381-
* `PAGES_API` represents all the API routes under `pages/api/`.
382-
*/
383-
PAGES_API = 'PAGES_API',
384-
/**
385-
* `APP_PAGE` represents all the React pages that are under `app/` with the
386-
* filename of `page.{j,t}s{,x}`.
387-
*/
388-
APP_PAGE = 'APP_PAGE',
389-
/**
390-
* `APP_ROUTE` represents all the API routes and metadata routes that are under `app/` with the
391-
* filename of `route.{j,t}s{,x}`.
392-
*/
393-
APP_ROUTE = 'APP_ROUTE',
394-
395-
/**
396-
* `IMAGE` represents all the images that are generated by `next/image`.
397-
*/
398-
// IMAGE = 'IMAGE',
399-
400-
/**
401-
* `STATIC_FILE` represents a static file (ie /_next/static)
402-
*/
403-
STATIC_FILE = 'STATIC_FILE',
404-
405-
MIDDLEWARE = 'MIDDLEWARE',
406-
}
407-
408375
export type ManifestRewriteRoute = ManifestBuiltRoute & Rewrite
409376
export type ManifestRedirectRoute = ManifestBuiltRoute & Redirect
410377
export type ManifestHeaderRoute = ManifestBuiltRoute & Header

0 commit comments

Comments
 (0)