diff --git a/packages/vinext/src/entries/pages-client-entry.ts b/packages/vinext/src/entries/pages-client-entry.ts index 654b4ef577..bd91e808c8 100644 --- a/packages/vinext/src/entries/pages-client-entry.ts +++ b/packages/vinext/src/entries/pages-client-entry.ts @@ -65,6 +65,7 @@ export async function generateClientEntry( const apiRoutes = await apiRouter(pagesDir, nextConfig?.pageExtensions, fileMatcher); const appFilePath = findFileWithExts(pagesDir, "_app", fileMatcher); + const errorFilePath = findFileWithExts(pagesDir, "_error", fileMatcher); const hasApp = appFilePath !== null; const appPrefetchRoutes = options.appPrefetchRoutes ?? []; const pagesPrefetchRoutes: VinextPagesLinkPrefetchRoute[] = [ @@ -102,6 +103,11 @@ export async function generateClientEntry( // lgtm[js/bad-code-sanitization] return ` ${JSON.stringify(nextFormatPattern)}: () => import(${JSON.stringify(absPath)})`; }); + loaderEntries.push( + errorFilePath !== null + ? ` "/_error": () => import(${JSON.stringify(errorFilePath)})` + : ' "/_error": () => import("next/error")', + ); const appFileBase = appFilePath ?? undefined; diff --git a/packages/vinext/src/entries/pages-server-entry.ts b/packages/vinext/src/entries/pages-server-entry.ts index 4432cc566a..e4bcc8d70e 100644 --- a/packages/vinext/src/entries/pages-server-entry.ts +++ b/packages/vinext/src/entries/pages-server-entry.ts @@ -95,7 +95,7 @@ export async function generateServerEntry( const errorImportCode = errorFilePath !== null ? `import * as ErrorPageModule from ${JSON.stringify(errorFilePath)};` - : `const ErrorPageModule = null;`; + : `import * as ErrorPageModule from "next/error";`; // Serialize i18n config for embedding in the server entry const i18nConfigJson = nextConfig?.i18n @@ -301,16 +301,14 @@ export const pageRoutes = [ ${pageRouteEntries.join(",\n")} ]; const _pageRouteTrie = _buildRouteTrie(pageRoutes); -const _errorPageRoute = ErrorPageModule - ? { - pattern: "/_error", - patternParts: ["_error"], - isDynamic: false, - params: [], - module: ErrorPageModule, - filePath: ${errorAssetPathJson}, - } - : null; +const _errorPageRoute = { + pattern: "/_error", + patternParts: ["_error"], + isDynamic: false, + params: [], + module: ErrorPageModule, + filePath: ${errorAssetPathJson}, +}; const apiRoutes = [ ${apiRouteEntries.join(",\n")} diff --git a/packages/vinext/src/server/dev-server.ts b/packages/vinext/src/server/dev-server.ts index dae2cbfcd7..eda6106883 100644 --- a/packages/vinext/src/server/dev-server.ts +++ b/packages/vinext/src/server/dev-server.ts @@ -69,6 +69,7 @@ import { type PagesStaticPathsEntry, } from "./pages-page-data.js"; import { createPagesDevAssetUrl, createPagesDevModuleUrl } from "./pages-dev-module-url.js"; +import { createPagesDevHydrationScript } from "./pages-dev-hydration.js"; import { getManifestFilesForModule } from "./pages-asset-tags.js"; import { isSerializableProps } from "./pages-serializable-props.js"; import { @@ -758,7 +759,23 @@ export function createSSRHandler( return; } // No route matched — try to render custom 404 page - await renderErrorPage(server, runner, req, res, url, pagesDir, 404, undefined, matcher); + const requestContext = createRequestContext(); + await runWithRequestContext(requestContext, async () => { + await _alsRegistration; + await renderErrorPage( + server, + runner, + req, + res, + url, + pagesDir, + 404, + undefined, + matcher, + undefined, + reactStrictMode, + ); + }); return; } @@ -975,6 +992,8 @@ export function createSSRHandler( 404, routerShim.wrapWithRouterContext, matcher, + undefined, + reactStrictMode, ); return; } @@ -1140,6 +1159,9 @@ export function createSSRHandler( pagesDir, 404, routerShim.wrapWithRouterContext, + undefined, + undefined, + reactStrictMode, ); return; } @@ -1591,6 +1613,9 @@ export function createSSRHandler( pagesDir, 404, routerShim.wrapWithRouterContext, + undefined, + undefined, + reactStrictMode, ); return; } @@ -1832,83 +1857,13 @@ export function createSSRHandler( }, }; - // Hydration entry: inline script that imports the page and hydrates. - // Stores the React root and page loader for client-side navigation. - const hydrationScript = ` -`; + const hydrationScript = createPagesDevHydrationScript({ + appModuleSource, + pageModuleSource, + reactStrictMode: reactStrictMode === true, + replaceFallbackRoute: true, + scriptNonce, + }); const nextDataScript = ``; + const errorHydrationScript = createPagesDevHydrationScript({ + appModuleSource, + forceRouterReady: true, + normalizePageProps: false, + pageModuleSource: errorModuleSource, + reactStrictMode: reactStrictMode === true, + scriptNonce, + setPagePatternsFromNextData: true, + }); + const errorScripts = `${errorNextDataScript}\n${errorHydrationScript}`; if (DocumentComponent) { - const errorPathname = candidate === "_error" ? "/_error" : `/${candidate}`; await streamPageToResponse(res, element, { url, server, fontHeadHTML: "", assetHeadHTML, - scripts: "", + scripts: errorScripts, DocumentComponent, statusCode, documentContext: { err, - pathname: errorPathname, + pathname: errorPage, query: parseQuery(url), asPath: url, req, @@ -2293,6 +2312,7 @@ async function renderErrorPage(