From e57c9059f4e0c9bc4af2a2826091a10b84512330 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 5 Jul 2023 13:42:43 -0600 Subject: [PATCH 01/14] feat: expose routeModule from app pages module --- .../build/webpack/loaders/next-app-loader.ts | 24 +++++ .../src/server/app-render/action-handler.ts | 6 +- .../next/src/server/app-render/app-render.tsx | 100 +++++++++++------- .../future/route-modules/app-page/module.ts | 67 ++++++++++++ 4 files changed, 152 insertions(+), 45 deletions(-) create mode 100644 packages/next/src/server/future/route-modules/app-page/module.ts diff --git a/packages/next/src/build/webpack/loaders/next-app-loader.ts b/packages/next/src/build/webpack/loaders/next-app-loader.ts index bbbf48fac56a7..54704cf5eb624 100644 --- a/packages/next/src/build/webpack/loaders/next-app-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-app-loader.ts @@ -1,6 +1,7 @@ import type webpack from 'webpack' import type { ValueOf } from '../../../shared/lib/constants' import type { ModuleReference, CollectedMetadata } from './metadata/types' +import type { AppPageRouteModuleOptions } from '../../../server/future/route-modules/app-page/module' import path from 'path' import { stringify } from 'querystring' @@ -665,9 +666,23 @@ const nextAppLoader: AppLoader = async function nextAppLoader() { } } + const options: Omit = { + definition: { + kind: RouteKind.APP_PAGE, + page: page, + pathname: page, + // The following aren't used in production. + bundlePath: '', + filename: '', + appPaths: [], + }, + } + // Prefer to modify next/src/server/app-render/entry-base.ts since this is shared with Turbopack. // Any changes to this code should be reflected in Turbopack's app_source.rs and/or app-renderer.tsx as well. const result = ` + import RouteModule from 'next/dist/server/future/route-modules/app-page/module' + export ${treeCodeResult.treeCode} export ${treeCodeResult.pages} export { default as GlobalError } from ${JSON.stringify( @@ -681,6 +696,15 @@ const nextAppLoader: AppLoader = async function nextAppLoader() { } export * from 'next/dist/server/app-render/entry-base' + + // Create and export the route module that will be consumed. + const options = ${JSON.stringify(options)} + export const routeModule = new RouteModule({ + ...options, + userland: { + loaderTree: tree, + }, + }) ` return result diff --git a/packages/next/src/server/app-render/action-handler.ts b/packages/next/src/server/app-render/action-handler.ts index df631e1cd6f9d..9da68cc09be14 100644 --- a/packages/next/src/server/app-render/action-handler.ts +++ b/packages/next/src/server/app-render/action-handler.ts @@ -242,6 +242,7 @@ export async function handleAction({ req, res, ComponentMod, + actionAsyncStorage, pathname, serverActionsManifest, generateFlight, @@ -252,6 +253,7 @@ export async function handleAction({ req: IncomingMessage res: ServerResponse ComponentMod: any + actionAsyncStorage: ActionAsyncStorage pathname: string serverActionsManifest: any generateFlight: (options: { @@ -294,10 +296,6 @@ export async function handleAction({ } ) - const { actionAsyncStorage } = ComponentMod as { - actionAsyncStorage: ActionAsyncStorage - } - let actionResult: RenderResult | undefined try { diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 3a4c6c9dcb0d4..2a3aa19c3e874 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -11,14 +11,9 @@ import type { Segment, } from './types' -import type { StaticGenerationAsyncStorage } from '../../client/components/static-generation-async-storage' -import type { StaticGenerationBailout } from '../../client/components/static-generation-bailout' -import type { RequestAsyncStorage } from '../../client/components/request-async-storage' - import React from 'react' import { NotFound as DefaultNotFound } from '../../client/components/error' import { createServerComponentRenderer } from './create-server-components-renderer' - import { ParsedUrlQuery } from 'querystring' import { NextParsedUrlQuery } from '../request-meta' import RenderResult, { type RenderResultMetadata } from '../render-result' @@ -137,12 +132,25 @@ function findDynamicParamFromRouterState( return null } -export async function renderToHTMLOrFlight( +type RenderOptsExtra = { + /** + * The tree created in next-app-loader that holds component segments and modules + */ + loaderTree: LoaderTree + + /** + * The exports from the entry base module. + */ + entryBase: typeof import('./entry-base') +} + +export async function renderToHTMLOrFlightImpl( req: IncomingMessage, res: ServerResponse, pagePath: string, query: NextParsedUrlQuery, - renderOpts: RenderOpts + renderOpts: RenderOpts, + extra: RenderOptsExtra ): Promise { const isFlight = req.headers[RSC.toLowerCase()] !== undefined const pathname = validateURL(req.url) @@ -159,6 +167,26 @@ export async function renderToHTMLOrFlight( serverActionsBodySizeLimit, } = renderOpts + const { + loaderTree, + entryBase: { + serverHooks: { DynamicServerError }, + AppRouter, + LayoutRouter, + staticGenerationAsyncStorage, + actionAsyncStorage, + requestAsyncStorage, + staticGenerationBailout, + RenderFromTemplateContext, + createSearchParamsBailoutProxy, + StaticGenerationSearchParamsBailoutProvider, + renderToReadableStream, + preloadFont, + preloadStyle, + preconnect, + }, + } = extra + const appUsingSizeAdjust = nextFontManifest?.appUsingSizeAdjust const clientReferenceManifest = renderOpts.clientReferenceManifest! @@ -205,13 +233,6 @@ export async function renderToHTMLOrFlight( */ const generateStaticHTML = supportsDynamicHTML !== true - const staticGenerationAsyncStorage: StaticGenerationAsyncStorage = - ComponentMod.staticGenerationAsyncStorage - const requestAsyncStorage: RequestAsyncStorage = - ComponentMod.requestAsyncStorage - const staticGenerationBailout: StaticGenerationBailout = - ComponentMod.staticGenerationBailout - // we wrap the render in an AsyncLocalStorage context const wrappedRender = async () => { const staticGenerationStore = staticGenerationAsyncStorage.getStore() @@ -246,11 +267,6 @@ export async function renderToHTMLOrFlight( ) : undefined - /** - * The tree created in next-app-loader that holds component segments and modules - */ - const loaderTree: LoaderTree = ComponentMod.tree - /** * The metadata items array created in next-app-loader with all relevant information * that we need to resolve the final metadata. @@ -261,15 +277,6 @@ export async function renderToHTMLOrFlight( ? crypto.randomUUID() : require('next/dist/compiled/nanoid').nanoid() - const LayoutRouter = - ComponentMod.LayoutRouter as typeof import('../../client/components/layout-router').default - const RenderFromTemplateContext = - ComponentMod.RenderFromTemplateContext as typeof import('../../client/components/render-from-template-context').default - const createSearchParamsBailoutProxy = - ComponentMod.createSearchParamsBailoutProxy as typeof import('../../client/components/searchparams-bailout-proxy').createSearchParamsBailoutProxy - const StaticGenerationSearchParamsBailoutProvider = - ComponentMod.StaticGenerationSearchParamsBailoutProvider as typeof import('../../client/components/static-generation-searchparams-bailout-provider').default - const isStaticGeneration = staticGenerationStore.isStaticGeneration // During static generation we need to call the static generation bailout when reading searchParams const providedSearchParams = isStaticGeneration @@ -466,16 +473,16 @@ export async function renderToHTMLOrFlight( const href = `${assetPrefix}/_next/${fontFilename}${getAssetQueryString( false )}` - ComponentMod.preloadFont(href, type) + preloadFont(href, type) } } else { try { let url = new URL(assetPrefix) - ComponentMod.preconnect(url.origin, 'anonymous') + preconnect(url.origin, 'anonymous') } catch (error) { // assetPrefix must not be a fully qualified domain name. We assume // we should preconnect to same origin instead - ComponentMod.preconnect('/', 'anonymous') + preconnect('/', 'anonymous') } } } @@ -501,7 +508,7 @@ export async function renderToHTMLOrFlight( const precedence = process.env.NODE_ENV === 'development' ? 'next_' + href : 'next' - ComponentMod.preloadStyle(fullHref) + preloadStyle(fullHref) return ( ) }, - ComponentMod, + { + renderToReadableStream, + __next_app__: ComponentMod.__next_app__, + }, serverComponentsRenderOpts, serverComponentsErrorHandler, nonce @@ -1600,6 +1603,7 @@ export async function renderToHTMLOrFlight( req, res, ComponentMod, + actionAsyncStorage, pathname: renderOpts.pathname, serverActionsManifest, generateFlight, @@ -1675,3 +1679,17 @@ export async function renderToHTMLOrFlight( ) ) } + +export async function renderToHTMLOrFlight( + req: IncomingMessage, + res: ServerResponse, + pagePath: string, + query: NextParsedUrlQuery, + renderOpts: RenderOpts +) { + return renderToHTMLOrFlightImpl(req, res, pagePath, query, renderOpts, { + loaderTree: renderOpts.ComponentMod.tree, + // The entry base exports are all exported from the component module. + entryBase: renderOpts.ComponentMod, + }) +} diff --git a/packages/next/src/server/future/route-modules/app-page/module.ts b/packages/next/src/server/future/route-modules/app-page/module.ts new file mode 100644 index 0000000000000..ec4d5996b0c0f --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/module.ts @@ -0,0 +1,67 @@ +import type { IncomingMessage, ServerResponse } from 'http' +import type { AppPageRouteDefinition } from '../../route-definitions/app-page-route-definition' +import type RenderResult from '../../../render-result' +import type { RenderOpts } from '../../../app-render/types' +import type { NextParsedUrlQuery } from '../../../request-meta' +import type { LoaderTree } from '../../../lib/app-dir-module' + +import { renderToHTMLOrFlightImpl } from '../../../app-render/app-render' +import { + RouteModule, + type RouteModuleOptions, + type RouteModuleHandleContext, +} from '../route-module' + +// These are imported weirdly like this because of the way that the bundling +// works. We need to import the built files from the dist directory, but we +// can't do that directly because we need types from the source files. So we +// import the types from the source files and then import the built files. +const entryBase = + require('next/dist/server/app-render/entry-base') as typeof import('../../../app-render/entry-base') + +type AppPageUserlandModule = { + /** + * The tree created in next-app-loader that holds component segments and modules + */ + loaderTree: LoaderTree +} + +interface AppPageRouteHandlerContext extends RouteModuleHandleContext { + pathname: string + query: NextParsedUrlQuery + renderOpts: RenderOpts +} + +export type AppPageRouteModuleOptions = RouteModuleOptions< + AppPageRouteDefinition, + AppPageUserlandModule +> + +export class AppPageRouteModule extends RouteModule< + AppPageRouteDefinition, + AppPageUserlandModule +> { + public handle(): Promise { + throw new Error('Method not implemented.') + } + + public render( + req: IncomingMessage, + res: ServerResponse, + context: AppPageRouteHandlerContext + ): Promise { + return renderToHTMLOrFlightImpl( + req, + res, + context.pathname, + context.query, + context.renderOpts, + { + loaderTree: this.userland.loaderTree, + entryBase, + } + ) + } +} + +export default AppPageRouteModule From d6ef6fbb4b749d36447cc9c0bc44320aed480e05 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 5 Jul 2023 14:39:15 -0600 Subject: [PATCH 02/14] fix: revert changes to render flow --- .../src/server/app-render/action-handler.ts | 6 +- .../next/src/server/app-render/app-render.tsx | 100 +++++++----------- .../future/route-modules/app-page/module.ts | 21 +--- 3 files changed, 50 insertions(+), 77 deletions(-) diff --git a/packages/next/src/server/app-render/action-handler.ts b/packages/next/src/server/app-render/action-handler.ts index 9da68cc09be14..df631e1cd6f9d 100644 --- a/packages/next/src/server/app-render/action-handler.ts +++ b/packages/next/src/server/app-render/action-handler.ts @@ -242,7 +242,6 @@ export async function handleAction({ req, res, ComponentMod, - actionAsyncStorage, pathname, serverActionsManifest, generateFlight, @@ -253,7 +252,6 @@ export async function handleAction({ req: IncomingMessage res: ServerResponse ComponentMod: any - actionAsyncStorage: ActionAsyncStorage pathname: string serverActionsManifest: any generateFlight: (options: { @@ -296,6 +294,10 @@ export async function handleAction({ } ) + const { actionAsyncStorage } = ComponentMod as { + actionAsyncStorage: ActionAsyncStorage + } + let actionResult: RenderResult | undefined try { diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 2a3aa19c3e874..3a4c6c9dcb0d4 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -11,9 +11,14 @@ import type { Segment, } from './types' +import type { StaticGenerationAsyncStorage } from '../../client/components/static-generation-async-storage' +import type { StaticGenerationBailout } from '../../client/components/static-generation-bailout' +import type { RequestAsyncStorage } from '../../client/components/request-async-storage' + import React from 'react' import { NotFound as DefaultNotFound } from '../../client/components/error' import { createServerComponentRenderer } from './create-server-components-renderer' + import { ParsedUrlQuery } from 'querystring' import { NextParsedUrlQuery } from '../request-meta' import RenderResult, { type RenderResultMetadata } from '../render-result' @@ -132,25 +137,12 @@ function findDynamicParamFromRouterState( return null } -type RenderOptsExtra = { - /** - * The tree created in next-app-loader that holds component segments and modules - */ - loaderTree: LoaderTree - - /** - * The exports from the entry base module. - */ - entryBase: typeof import('./entry-base') -} - -export async function renderToHTMLOrFlightImpl( +export async function renderToHTMLOrFlight( req: IncomingMessage, res: ServerResponse, pagePath: string, query: NextParsedUrlQuery, - renderOpts: RenderOpts, - extra: RenderOptsExtra + renderOpts: RenderOpts ): Promise { const isFlight = req.headers[RSC.toLowerCase()] !== undefined const pathname = validateURL(req.url) @@ -167,26 +159,6 @@ export async function renderToHTMLOrFlightImpl( serverActionsBodySizeLimit, } = renderOpts - const { - loaderTree, - entryBase: { - serverHooks: { DynamicServerError }, - AppRouter, - LayoutRouter, - staticGenerationAsyncStorage, - actionAsyncStorage, - requestAsyncStorage, - staticGenerationBailout, - RenderFromTemplateContext, - createSearchParamsBailoutProxy, - StaticGenerationSearchParamsBailoutProvider, - renderToReadableStream, - preloadFont, - preloadStyle, - preconnect, - }, - } = extra - const appUsingSizeAdjust = nextFontManifest?.appUsingSizeAdjust const clientReferenceManifest = renderOpts.clientReferenceManifest! @@ -233,6 +205,13 @@ export async function renderToHTMLOrFlightImpl( */ const generateStaticHTML = supportsDynamicHTML !== true + const staticGenerationAsyncStorage: StaticGenerationAsyncStorage = + ComponentMod.staticGenerationAsyncStorage + const requestAsyncStorage: RequestAsyncStorage = + ComponentMod.requestAsyncStorage + const staticGenerationBailout: StaticGenerationBailout = + ComponentMod.staticGenerationBailout + // we wrap the render in an AsyncLocalStorage context const wrappedRender = async () => { const staticGenerationStore = staticGenerationAsyncStorage.getStore() @@ -267,6 +246,11 @@ export async function renderToHTMLOrFlightImpl( ) : undefined + /** + * The tree created in next-app-loader that holds component segments and modules + */ + const loaderTree: LoaderTree = ComponentMod.tree + /** * The metadata items array created in next-app-loader with all relevant information * that we need to resolve the final metadata. @@ -277,6 +261,15 @@ export async function renderToHTMLOrFlightImpl( ? crypto.randomUUID() : require('next/dist/compiled/nanoid').nanoid() + const LayoutRouter = + ComponentMod.LayoutRouter as typeof import('../../client/components/layout-router').default + const RenderFromTemplateContext = + ComponentMod.RenderFromTemplateContext as typeof import('../../client/components/render-from-template-context').default + const createSearchParamsBailoutProxy = + ComponentMod.createSearchParamsBailoutProxy as typeof import('../../client/components/searchparams-bailout-proxy').createSearchParamsBailoutProxy + const StaticGenerationSearchParamsBailoutProvider = + ComponentMod.StaticGenerationSearchParamsBailoutProvider as typeof import('../../client/components/static-generation-searchparams-bailout-provider').default + const isStaticGeneration = staticGenerationStore.isStaticGeneration // During static generation we need to call the static generation bailout when reading searchParams const providedSearchParams = isStaticGeneration @@ -473,16 +466,16 @@ export async function renderToHTMLOrFlightImpl( const href = `${assetPrefix}/_next/${fontFilename}${getAssetQueryString( false )}` - preloadFont(href, type) + ComponentMod.preloadFont(href, type) } } else { try { let url = new URL(assetPrefix) - preconnect(url.origin, 'anonymous') + ComponentMod.preconnect(url.origin, 'anonymous') } catch (error) { // assetPrefix must not be a fully qualified domain name. We assume // we should preconnect to same origin instead - preconnect('/', 'anonymous') + ComponentMod.preconnect('/', 'anonymous') } } } @@ -508,7 +501,7 @@ export async function renderToHTMLOrFlightImpl( const precedence = process.env.NODE_ENV === 'development' ? 'next_' + href : 'next' - preloadStyle(fullHref) + ComponentMod.preloadStyle(fullHref) return ( ) }, - { - renderToReadableStream, - __next_app__: ComponentMod.__next_app__, - }, + ComponentMod, serverComponentsRenderOpts, serverComponentsErrorHandler, nonce @@ -1603,7 +1600,6 @@ export async function renderToHTMLOrFlightImpl( req, res, ComponentMod, - actionAsyncStorage, pathname: renderOpts.pathname, serverActionsManifest, generateFlight, @@ -1679,17 +1675,3 @@ export async function renderToHTMLOrFlightImpl( ) ) } - -export async function renderToHTMLOrFlight( - req: IncomingMessage, - res: ServerResponse, - pagePath: string, - query: NextParsedUrlQuery, - renderOpts: RenderOpts -) { - return renderToHTMLOrFlightImpl(req, res, pagePath, query, renderOpts, { - loaderTree: renderOpts.ComponentMod.tree, - // The entry base exports are all exported from the component module. - entryBase: renderOpts.ComponentMod, - }) -} diff --git a/packages/next/src/server/future/route-modules/app-page/module.ts b/packages/next/src/server/future/route-modules/app-page/module.ts index ec4d5996b0c0f..870465551d915 100644 --- a/packages/next/src/server/future/route-modules/app-page/module.ts +++ b/packages/next/src/server/future/route-modules/app-page/module.ts @@ -5,20 +5,13 @@ import type { RenderOpts } from '../../../app-render/types' import type { NextParsedUrlQuery } from '../../../request-meta' import type { LoaderTree } from '../../../lib/app-dir-module' -import { renderToHTMLOrFlightImpl } from '../../../app-render/app-render' +import { renderToHTMLOrFlight } from '../../../app-render/app-render' import { RouteModule, type RouteModuleOptions, type RouteModuleHandleContext, } from '../route-module' -// These are imported weirdly like this because of the way that the bundling -// works. We need to import the built files from the dist directory, but we -// can't do that directly because we need types from the source files. So we -// import the types from the source files and then import the built files. -const entryBase = - require('next/dist/server/app-render/entry-base') as typeof import('../../../app-render/entry-base') - type AppPageUserlandModule = { /** * The tree created in next-app-loader that holds component segments and modules @@ -27,7 +20,7 @@ type AppPageUserlandModule = { } interface AppPageRouteHandlerContext extends RouteModuleHandleContext { - pathname: string + page: string query: NextParsedUrlQuery renderOpts: RenderOpts } @@ -50,16 +43,12 @@ export class AppPageRouteModule extends RouteModule< res: ServerResponse, context: AppPageRouteHandlerContext ): Promise { - return renderToHTMLOrFlightImpl( + return renderToHTMLOrFlight( req, res, - context.pathname, + context.page, context.query, - context.renderOpts, - { - loaderTree: this.userland.loaderTree, - entryBase, - } + context.renderOpts ) } } From 3432a5f1d7ebaa8f79dd723344253208082ba47d Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 5 Jul 2023 14:43:08 -0600 Subject: [PATCH 03/14] feat: enable module rendering mode --- packages/next/src/server/base-server.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index acc493b53772e..9e66fcd5e3743 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -28,6 +28,7 @@ import type { PrerenderManifest } from '../build' import type { ClientReferenceManifest } from '../build/webpack/plugins/flight-manifest-plugin' import type { NextFontManifest } from '../build/webpack/plugins/next-font-manifest-plugin' import type { PagesRouteModule } from './future/route-modules/pages/module' +import type { AppPageRouteModule } from './future/route-modules/app-page/module' import type { NodeNextRequest, NodeNextResponse } from './base-http/node' import type { AppRouteRouteMatch } from './future/route-matches/app-route-route-match' import type { RouteDefinition } from './future/route-definitions/route-definition' @@ -1788,6 +1789,26 @@ export default abstract class Server { // https://github.com/vercel/next.js/blob/df7cbd904c3bd85f399d1ce90680c0ecf92d2752/packages/next/server/render.tsx#L947-L952 renderOpts.nextFontManifest = this.nextFontManifest + // Call the built-in render method on the module. + result = await module.render( + (req as NodeNextRequest).originalRequest ?? (req as WebNextRequest), + (res as NodeNextResponse).originalResponse ?? + (res as WebNextResponse), + { page: pathname, params: match.params, query, renderOpts } + ) + } else if ( + match && + isRouteMatch(match, RouteKind.APP_PAGE) && + components.routeModule + ) { + const module = components.routeModule as AppPageRouteModule + + // Due to the way we pass data by mutating `renderOpts`, we can't extend the + // object here but only updating its `clientReferenceManifest` field. + // https://github.com/vercel/next.js/blob/df7cbd904c3bd85f399d1ce90680c0ecf92d2752/packages/next/server/render.tsx#L947-L952 + renderOpts.clientReferenceManifest = this.clientReferenceManifest + renderOpts.nextFontManifest = this.nextFontManifest + // Call the built-in render method on the module. result = await module.render( (req as NodeNextRequest).originalRequest ?? (req as WebNextRequest), From dab75e3ed59d301072b1735122344c6feef9335f Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 5 Jul 2023 14:53:34 -0600 Subject: [PATCH 04/14] feat: use correct normalizers --- .../next/src/build/webpack/loaders/next-app-loader.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/next/src/build/webpack/loaders/next-app-loader.ts b/packages/next/src/build/webpack/loaders/next-app-loader.ts index 54704cf5eb624..bf841fbba9d43 100644 --- a/packages/next/src/build/webpack/loaders/next-app-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-app-loader.ts @@ -666,13 +666,16 @@ const nextAppLoader: AppLoader = async function nextAppLoader() { } } + const pathname = new AppPathnameNormalizer().normalize(page) + const bundlePath = new AppBundlePathNormalizer().normalize(page) + const options: Omit = { definition: { kind: RouteKind.APP_PAGE, - page: page, - pathname: page, + page, + pathname, + bundlePath, // The following aren't used in production. - bundlePath: '', filename: '', appPaths: [], }, From 85885d8e98062263cd28f2f29eb9039d177df0a4 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 5 Jul 2023 15:35:15 -0600 Subject: [PATCH 05/14] fix: moved app page module bundling to shared layer --- packages/next/src/build/webpack-config.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index a741221c661a7..f428a2dac5711 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -1944,6 +1944,12 @@ export default async function getBaseWebpackConfig( layer: WEBPACK_LAYERS.shared, test: asyncStoragesRegex, }, + { + // Ensure that the app page module is shared between server and + // client layers, this enables React to work correctly for RSC. + layer: WEBPACK_LAYERS.shared, + test: /next[\\/]dist[\\/](esm[\\/])?server[\\/]future[\\/]route-modules[\\/]app-page[\\/]module/, + }, { // All app dir layers need to use this configured resolution logic issuerLayer: { From 1cfc5016c0de1a9f2c23c76f905a0fcd4b9c413f Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 7 Jul 2023 13:09:12 -0600 Subject: [PATCH 06/14] fix: moved react to client layer --- packages/next/src/build/webpack-config.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index f428a2dac5711..9ecce644d4fe2 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -1945,9 +1945,9 @@ export default async function getBaseWebpackConfig( test: asyncStoragesRegex, }, { - // Ensure that the app page module is shared between server and - // client layers, this enables React to work correctly for RSC. - layer: WEBPACK_LAYERS.shared, + // Ensure that the app page module is in the client layers, this + // enables React to work correctly for RSC. + layer: WEBPACK_LAYERS.client, test: /next[\\/]dist[\\/](esm[\\/])?server[\\/]future[\\/]route-modules[\\/]app-page[\\/]module/, }, { From 5af6b7eb7a193bdadad8af9417b18e76c5ebcfe7 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 7 Jul 2023 15:14:15 -0600 Subject: [PATCH 07/14] fix: fixed route module detection --- packages/next/src/build/utils.ts | 35 ++++++++++--------- .../future/route-modules/app-route/module.ts | 5 +++ .../e2e/app-dir/app-static/app-static.test.ts | 10 +++--- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index de9e77b0acab7..cfb36fe4898fd 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -14,8 +14,8 @@ import type { EdgeFunctionDefinition, MiddlewareManifest, } from './webpack/plugins/middleware-plugin' -import type { AppRouteUserlandModule } from '../server/future/route-modules/app-route/module' import type { StaticGenerationAsyncStorage } from '../client/components/static-generation-async-storage' +import type { RouteModule } from '../server/future/route-modules/route-module' import '../server/require-hook' import '../server/node-polyfill-fetch' @@ -66,6 +66,7 @@ import { nodeFs } from '../server/lib/node-fs-methods' import * as ciEnvironment from '../telemetry/ci-info' import { normalizeAppPath } from '../shared/lib/router/utils/app-paths' import { denormalizeAppPagePath } from '../shared/lib/page-path/denormalize-app-path' +import { AppRouteRouteModule } from '../server/future/route-modules/app-route/module' export type ROUTER_TYPE = 'pages' | 'app' @@ -1438,10 +1439,6 @@ export async function isPageStatic({ isClientComponent = isClientReference(componentsResult.ComponentMod) const tree = componentsResult.ComponentMod.tree - // This is present on the new route modules. - const userland: AppRouteUserlandModule | undefined = - componentsResult.routeModule?.userland - const staticGenerationAsyncStorage: StaticGenerationAsyncStorage = componentsResult.ComponentMod.staticGenerationAsyncStorage if (!staticGenerationAsyncStorage) { @@ -1457,19 +1454,23 @@ export async function isPageStatic({ ) } - const generateParams: GenerateParams = userland - ? [ - { - config: { - revalidate: userland.revalidate, - dynamic: userland.dynamic, - dynamicParams: userland.dynamicParams, + const { routeModule } = componentsResult + + const generateParams: GenerateParams = + routeModule && AppRouteRouteModule.is(routeModule) + ? [ + { + config: { + revalidate: routeModule.userland.revalidate, + dynamic: routeModule.userland.dynamic, + dynamicParams: routeModule.userland.dynamicParams, + }, + generateStaticParams: + routeModule.userland.generateStaticParams, + segmentPath: page, }, - generateStaticParams: userland.generateStaticParams, - segmentPath: page, - }, - ] - : await collectGenerateParams(tree) + ] + : await collectGenerateParams(tree) appConfig = generateParams.reduce( (builtConfig: AppConfig, curGenParams): AppConfig => { diff --git a/packages/next/src/server/future/route-modules/app-route/module.ts b/packages/next/src/server/future/route-modules/app-route/module.ts index bcc7e19a80956..5ffb9170cf373 100644 --- a/packages/next/src/server/future/route-modules/app-route/module.ts +++ b/packages/next/src/server/future/route-modules/app-route/module.ts @@ -32,6 +32,7 @@ import * as Log from '../../../../build/output/log' import { autoImplementMethods } from './helpers/auto-implement-methods' import { getNonStaticMethods } from './helpers/get-non-static-methods' import { appendMutableCookies } from '../../../web/spec-extension/adapters/request-cookies' +import { RouteKind } from '../../route-kind' // These are imported weirdly like this because of the way that the bundling // works. We need to import the built files from the dist directory, but we @@ -158,6 +159,10 @@ export class AppRouteRouteModule extends RouteModule< private readonly nonStaticMethods: ReadonlyArray | false private readonly dynamic: AppRouteUserlandModule['dynamic'] + public static is(route: RouteModule): route is AppRouteRouteModule { + return route.definition.kind === RouteKind.APP_ROUTE + } + constructor({ userland, definition, diff --git a/test/e2e/app-dir/app-static/app-static.test.ts b/test/e2e/app-dir/app-static/app-static.test.ts index 41d6ab09bc57c..b10f9d7d2d7ef 100644 --- a/test/e2e/app-dir/app-static/app-static.test.ts +++ b/test/e2e/app-dir/app-static/app-static.test.ts @@ -162,11 +162,13 @@ createNextDescribe( } ` ) - const html = await next.render('/invalid/first') + + // The page may take a moment to compile, so try it a few times. + check(async () => { + return next.render('/invalid/first') + }, /A required parameter \(slug\) was not provided as a string received object/) + await next.deleteFile('app/invalid/[slug]/page.js') - expect(html).toContain( - 'A required parameter (slug) was not provided as a string received object' - ) }) it('should correctly handle multi-level generateStaticParams when some levels are missing', async () => { From 9c47387b0c9794ebccc57af73eff743b2af56f6e Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 7 Jul 2023 15:20:51 -0600 Subject: [PATCH 08/14] fix: linting --- packages/next/src/build/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index cfb36fe4898fd..476a7e5c9308d 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -15,7 +15,6 @@ import type { MiddlewareManifest, } from './webpack/plugins/middleware-plugin' import type { StaticGenerationAsyncStorage } from '../client/components/static-generation-async-storage' -import type { RouteModule } from '../server/future/route-modules/route-module' import '../server/require-hook' import '../server/node-polyfill-fetch' From 7e08b5b85834a99cb6a734bb521b010cfee150f4 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 10 Jul 2023 11:24:53 -0600 Subject: [PATCH 09/14] fix: fixed detection of static params from static path analysis --- packages/next/src/build/utils.ts | 1 + .../src/server/dev/static-paths-worker.ts | 31 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index 476a7e5c9308d..8ee360f1d25a1 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -20,6 +20,7 @@ import '../server/require-hook' import '../server/node-polyfill-fetch' import '../server/node-polyfill-crypto' import '../server/node-environment' + import chalk from 'next/dist/compiled/chalk' import getGzipSize from 'next/dist/compiled/gzip-size' import textTable from 'next/dist/compiled/text-table' diff --git a/packages/next/src/server/dev/static-paths-worker.ts b/packages/next/src/server/dev/static-paths-worker.ts index cf6a168faf0a3..5e6a7f9956dff 100644 --- a/packages/next/src/server/dev/static-paths-worker.ts +++ b/packages/next/src/server/dev/static-paths-worker.ts @@ -1,9 +1,9 @@ import type { NextConfigComplete } from '../config-shared' -import type { AppRouteUserlandModule } from '../future/route-modules/app-route/module' import '../require-hook' import '../node-polyfill-fetch' import '../node-environment' + import { buildAppStaticPaths, buildStaticPaths, @@ -15,6 +15,7 @@ import { setHttpClientAndAgentOptions } from '../config' import { IncrementalCache } from '../lib/incremental-cache' import * as serverHooks from '../../client/components/hooks-server-context' import { staticGenerationAsyncStorage } from '../../client/components/static-generation-async-storage' +import { AppRouteRouteModule } from '../future/route-modules/app-route/module' type RuntimeConfig = any @@ -76,21 +77,21 @@ export async function loadStaticPaths({ } if (isAppPath) { - const userland: AppRouteUserlandModule | undefined = - components.routeModule?.userland - const generateParams: GenerateParams = userland - ? [ - { - config: { - revalidate: userland.revalidate, - dynamic: userland.dynamic, - dynamicParams: userland.dynamicParams, + const { routeModule } = components + const generateParams: GenerateParams = + routeModule && AppRouteRouteModule.is(routeModule) + ? [ + { + config: { + revalidate: routeModule.userland.revalidate, + dynamic: routeModule.userland.dynamic, + dynamicParams: routeModule.userland.dynamicParams, + }, + generateStaticParams: routeModule.userland.generateStaticParams, + segmentPath: pathname, }, - generateStaticParams: userland.generateStaticParams, - segmentPath: pathname, - }, - ] - : await collectGenerateParams(components.ComponentMod.tree) + ] + : await collectGenerateParams(components.ComponentMod.tree) return await buildAppStaticPaths({ page: pathname, From b4696df3d2f27b75105c4bc914f795ad44f8dbf2 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 10 Jul 2023 11:33:06 -0600 Subject: [PATCH 10/14] fix: linting --- packages/next/src/server/base-server.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 87ac4f28033ce..d2eadc9c165de 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -1801,7 +1801,6 @@ export default abstract class Server { // Due to the way we pass data by mutating `renderOpts`, we can't extend the // object here but only updating its `clientReferenceManifest` field. // https://github.com/vercel/next.js/blob/df7cbd904c3bd85f399d1ce90680c0ecf92d2752/packages/next/server/render.tsx#L947-L952 - renderOpts.clientReferenceManifest = this.clientReferenceManifest renderOpts.nextFontManifest = this.nextFontManifest // Call the built-in render method on the module. From e341d17e52cac060bf0dbaa72abcc0aab6c9092b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 10 Jul 2023 18:11:58 -0600 Subject: [PATCH 11/14] fix: pass fonts manifest to extendedRenderOpts --- .../build/webpack/loaders/next-edge-ssr-loader/render.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts b/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts index 9a6105b16b46d..e4fe144e27787 100644 --- a/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts +++ b/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts @@ -4,7 +4,7 @@ import type { DocumentType, AppType } from '../../../../shared/lib/utils' import type { BuildManifest } from '../../../../server/get-page-files' import type { ReactLoadableManifest } from '../../../../server/load-components' import type { ClientReferenceManifest } from '../../plugins/flight-manifest-plugin' -import type { NextFontManifestPlugin } from '../../plugins/next-font-manifest-plugin' +import type { NextFontManifest } from '../../plugins/next-font-manifest-plugin' import WebServer from '../../../../server/web-server' import { @@ -57,7 +57,7 @@ export function getRender({ appServerMod: any config: NextConfigComplete buildId: string - nextFontManifest: NextFontManifestPlugin + nextFontManifest: NextFontManifest incrementalCacheHandler?: any }) { const isAppPath = pagesType === 'app' @@ -67,7 +67,6 @@ export function getRender({ prerenderManifest, reactLoadableManifest, subresourceIntegrityManifest, - nextFontManifest, Document, App: appMod?.default as AppType, clientReferenceManifest, @@ -89,6 +88,7 @@ export function getRender({ disableOptimizedLoading: true, serverActionsManifest, serverActionsBodySizeLimit, + nextFontManifest, }, renderToHTML, incrementalCacheHandler, From 4595f14d20b29f1f048c866843dc9c4067a3881c Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 11 Jul 2023 12:03:13 -0600 Subject: [PATCH 12/14] fix: enhanced types for render and fixed race with test --- .../loaders/next-edge-ssr-loader/render.ts | 131 ++++++++++++------ .../e2e/app-dir/app-static/app-static.test.ts | 2 +- 2 files changed, 86 insertions(+), 47 deletions(-) diff --git a/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts b/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts index e4fe144e27787..3482c88ea24d7 100644 --- a/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts +++ b/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts @@ -2,7 +2,10 @@ import type { NextConfigComplete } from '../../../../server/config-shared' import type { DocumentType, AppType } from '../../../../shared/lib/utils' import type { BuildManifest } from '../../../../server/get-page-files' -import type { ReactLoadableManifest } from '../../../../server/load-components' +import type { + LoadComponentsReturnType, + ReactLoadableManifest, +} from '../../../../server/load-components' import type { ClientReferenceManifest } from '../../plugins/flight-manifest-plugin' import type { NextFontManifest } from '../../plugins/next-font-manifest-plugin' @@ -16,6 +19,71 @@ import { PrerenderManifest } from '../../..' import { normalizeAppPath } from '../../../../shared/lib/router/utils/app-paths' import { SizeLimit } from '../../../../../types' +type LoadPageComponents = Pick< + LoadComponentsReturnType, + | 'Component' + | 'pageConfig' + | 'pageConfig' + | 'getStaticPaths' + | 'getStaticProps' + | 'getServerSideProps' + | 'ComponentMod' + | 'routeModule' + | 'isAppPath' + | 'pathname' +> + +function loadPageComponents( + page: string, + pathname: string, + pageMod: any, + errorMod: any, + error500Mod: any +): LoadPageComponents | null { + if (pathname === page) { + return { + Component: pageMod.default, + pageConfig: pageMod.config || {}, + getStaticProps: pageMod.getStaticProps, + getServerSideProps: pageMod.getServerSideProps, + getStaticPaths: pageMod.getStaticPaths, + ComponentMod: pageMod, + isAppPath: !!pageMod.__next_app__, + pathname, + routeModule: pageMod.routeModule, + } + } + + // If there is a custom 500 page, we need to handle it separately. + if (pathname === '/500' && error500Mod) { + return { + Component: error500Mod.default, + pageConfig: error500Mod.config || {}, + getStaticProps: error500Mod.getStaticProps, + getServerSideProps: error500Mod.getServerSideProps, + getStaticPaths: error500Mod.getStaticPaths, + ComponentMod: error500Mod, + pathname, + routeModule: error500Mod.routeModule, + } + } + + if (pathname === '/_error') { + return { + Component: errorMod.default, + pageConfig: errorMod.config || {}, + getStaticProps: errorMod.getStaticProps, + getServerSideProps: errorMod.getServerSideProps, + getStaticPaths: errorMod.getStaticPaths, + ComponentMod: errorMod, + pathname, + routeModule: errorMod.routeModule, + } + } + + return null +} + export function getRender({ dev, page, @@ -61,10 +129,11 @@ export function getRender({ incrementalCacheHandler?: any }) { const isAppPath = pagesType === 'app' - const baseLoadComponentResult = { - dev, + const baseLoadComponentResult: Omit< + LoadComponentsReturnType, + keyof LoadPageComponents + > = { buildManifest, - prerenderManifest, reactLoadableManifest, subresourceIntegrityManifest, Document, @@ -92,49 +161,19 @@ export function getRender({ }, renderToHTML, incrementalCacheHandler, - loadComponent: async (pathname) => { - if (pathname === page) { - return { - ...baseLoadComponentResult, - Component: pageMod.default, - pageConfig: pageMod.config || {}, - getStaticProps: pageMod.getStaticProps, - getServerSideProps: pageMod.getServerSideProps, - getStaticPaths: pageMod.getStaticPaths, - ComponentMod: pageMod, - isAppPath: !!pageMod.__next_app__, - pathname, - routeModule: pageMod.routeModule, - } - } - - // If there is a custom 500 page, we need to handle it separately. - if (pathname === '/500' && error500Mod) { - return { - ...baseLoadComponentResult, - Component: error500Mod.default, - pageConfig: error500Mod.config || {}, - getStaticProps: error500Mod.getStaticProps, - getServerSideProps: error500Mod.getServerSideProps, - getStaticPaths: error500Mod.getStaticPaths, - ComponentMod: error500Mod, - pathname, - routeModule: error500Mod.routeModule, - } - } + loadComponent: async ( + pathname + ): Promise => { + const components = loadPageComponents( + page, + pathname, + pageMod, + errorMod, + error500Mod + ) - if (pathname === '/_error') { - return { - ...baseLoadComponentResult, - Component: errorMod.default, - pageConfig: errorMod.config || {}, - getStaticProps: errorMod.getStaticProps, - getServerSideProps: errorMod.getServerSideProps, - getStaticPaths: errorMod.getStaticPaths, - ComponentMod: errorMod, - pathname, - routeModule: errorMod.routeModule, - } + if (components) { + return { ...components, ...baseLoadComponentResult } } return null diff --git a/test/e2e/app-dir/app-static/app-static.test.ts b/test/e2e/app-dir/app-static/app-static.test.ts index bc45247f886e3..aa83b42983d86 100644 --- a/test/e2e/app-dir/app-static/app-static.test.ts +++ b/test/e2e/app-dir/app-static/app-static.test.ts @@ -164,7 +164,7 @@ createNextDescribe( ) // The page may take a moment to compile, so try it a few times. - check(async () => { + await check(async () => { return next.render('/invalid/first') }, /A required parameter \(slug\) was not provided as a string received object/) From d671c2439559cb675176782d2380511338f1485f Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 11 Jul 2023 17:00:51 -0600 Subject: [PATCH 13/14] fix: revert changes to render --- .../loaders/next-edge-ssr-loader/render.ts | 129 ++++++------------ 1 file changed, 44 insertions(+), 85 deletions(-) diff --git a/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts b/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts index 3482c88ea24d7..7b8e77303275c 100644 --- a/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts +++ b/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts @@ -2,10 +2,7 @@ import type { NextConfigComplete } from '../../../../server/config-shared' import type { DocumentType, AppType } from '../../../../shared/lib/utils' import type { BuildManifest } from '../../../../server/get-page-files' -import type { - LoadComponentsReturnType, - ReactLoadableManifest, -} from '../../../../server/load-components' +import type { ReactLoadableManifest } from '../../../../server/load-components' import type { ClientReferenceManifest } from '../../plugins/flight-manifest-plugin' import type { NextFontManifest } from '../../plugins/next-font-manifest-plugin' @@ -19,71 +16,6 @@ import { PrerenderManifest } from '../../..' import { normalizeAppPath } from '../../../../shared/lib/router/utils/app-paths' import { SizeLimit } from '../../../../../types' -type LoadPageComponents = Pick< - LoadComponentsReturnType, - | 'Component' - | 'pageConfig' - | 'pageConfig' - | 'getStaticPaths' - | 'getStaticProps' - | 'getServerSideProps' - | 'ComponentMod' - | 'routeModule' - | 'isAppPath' - | 'pathname' -> - -function loadPageComponents( - page: string, - pathname: string, - pageMod: any, - errorMod: any, - error500Mod: any -): LoadPageComponents | null { - if (pathname === page) { - return { - Component: pageMod.default, - pageConfig: pageMod.config || {}, - getStaticProps: pageMod.getStaticProps, - getServerSideProps: pageMod.getServerSideProps, - getStaticPaths: pageMod.getStaticPaths, - ComponentMod: pageMod, - isAppPath: !!pageMod.__next_app__, - pathname, - routeModule: pageMod.routeModule, - } - } - - // If there is a custom 500 page, we need to handle it separately. - if (pathname === '/500' && error500Mod) { - return { - Component: error500Mod.default, - pageConfig: error500Mod.config || {}, - getStaticProps: error500Mod.getStaticProps, - getServerSideProps: error500Mod.getServerSideProps, - getStaticPaths: error500Mod.getStaticPaths, - ComponentMod: error500Mod, - pathname, - routeModule: error500Mod.routeModule, - } - } - - if (pathname === '/_error') { - return { - Component: errorMod.default, - pageConfig: errorMod.config || {}, - getStaticProps: errorMod.getStaticProps, - getServerSideProps: errorMod.getServerSideProps, - getStaticPaths: errorMod.getStaticPaths, - ComponentMod: errorMod, - pathname, - routeModule: errorMod.routeModule, - } - } - - return null -} - export function getRender({ dev, page, @@ -129,10 +61,7 @@ export function getRender({ incrementalCacheHandler?: any }) { const isAppPath = pagesType === 'app' - const baseLoadComponentResult: Omit< - LoadComponentsReturnType, - keyof LoadPageComponents - > = { + const baseLoadComponentResult = { buildManifest, reactLoadableManifest, subresourceIntegrityManifest, @@ -161,19 +90,49 @@ export function getRender({ }, renderToHTML, incrementalCacheHandler, - loadComponent: async ( - pathname - ): Promise => { - const components = loadPageComponents( - page, - pathname, - pageMod, - errorMod, - error500Mod - ) + loadComponent: async (pathname) => { + if (pathname === page) { + return { + ...baseLoadComponentResult, + Component: pageMod.default, + pageConfig: pageMod.config || {}, + getStaticProps: pageMod.getStaticProps, + getServerSideProps: pageMod.getServerSideProps, + getStaticPaths: pageMod.getStaticPaths, + ComponentMod: pageMod, + isAppPath: !!pageMod.__next_app__, + pathname, + routeModule: pageMod.routeModule, + } + } + + // If there is a custom 500 page, we need to handle it separately. + if (pathname === '/500' && error500Mod) { + return { + ...baseLoadComponentResult, + Component: error500Mod.default, + pageConfig: error500Mod.config || {}, + getStaticProps: error500Mod.getStaticProps, + getServerSideProps: error500Mod.getServerSideProps, + getStaticPaths: error500Mod.getStaticPaths, + ComponentMod: error500Mod, + pathname, + routeModule: error500Mod.routeModule, + } + } - if (components) { - return { ...components, ...baseLoadComponentResult } + if (pathname === '/_error') { + return { + ...baseLoadComponentResult, + Component: errorMod.default, + pageConfig: errorMod.config || {}, + getStaticProps: errorMod.getStaticProps, + getServerSideProps: errorMod.getServerSideProps, + getStaticPaths: errorMod.getStaticPaths, + ComponentMod: errorMod, + pathname, + routeModule: errorMod.routeModule, + } } return null From 4fc31aa6e83dd3c49d3d45e9a39cc301e6434ed7 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 12 Jul 2023 10:20:44 -0600 Subject: [PATCH 14/14] fix: fixed load components return type --- .../src/build/webpack/loaders/next-edge-ssr-loader/render.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts b/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts index 7b8e77303275c..f97f1577fbca7 100644 --- a/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts +++ b/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts @@ -62,6 +62,7 @@ export function getRender({ }) { const isAppPath = pagesType === 'app' const baseLoadComponentResult = { + dev, buildManifest, reactLoadableManifest, subresourceIntegrityManifest,