diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index 42492d511d85354..4b241a0c81ed4a3 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -160,14 +160,10 @@ export function createEntrypoints( name: '[name].js', value: `next-middleware-ssr-loader?${stringify({ page, - absoluteAppPath: pages['/_app'], - absoluteDocumentPath: pages['/_document'], - absoluteErrorPath: pages['/500'] || pages['/_error'], + absolute500Path: pages['/500'] || '', absolutePagePath, isServerComponent: isFlight, - buildId, - basePath: config.basePath, - assetPrefix: config.assetPrefix, + ...defaultServerlessOptions, } as any)}!`, isServer: false, isServerWeb: true, diff --git a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts index 096a4c06eac9b27..8d7d94a02f32e96 100644 --- a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts +++ b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts @@ -5,159 +5,61 @@ export default async function middlewareRSCLoader(this: any) { absolutePagePath, absoluteAppPath, absoluteDocumentPath, + absolute500Path, absoluteErrorPath, - basePath, - isServerComponent: isServerComponentQuery, - assetPrefix, - buildId, + isServerComponent, + ...restRenderOpts } = this.getOptions() - const isServerComponent = isServerComponentQuery === 'true' const stringifiedAbsolutePagePath = stringifyRequest(this, absolutePagePath) const stringifiedAbsoluteAppPath = stringifyRequest(this, absoluteAppPath) - const stringifiedAbsoluteErrorPath = stringifyRequest(this, absoluteErrorPath) + const stringifiedAbsolute500PagePath = stringifyRequest( + this, + absolute500Path || absoluteErrorPath + ) const stringifiedAbsoluteDocumentPath = stringifyRequest( this, absoluteDocumentPath ) const transformed = ` - import { adapter } from 'next/dist/server/web/adapter' - import { RouterContext } from 'next/dist/shared/lib/router-context' - import { renderToHTML } from 'next/dist/server/web/render' - - import App from ${stringifiedAbsoluteAppPath} - import Document from ${stringifiedAbsoluteDocumentPath} - const errorMod = require(${stringifiedAbsoluteErrorPath}) - - const { - default: Page, - config, - getStaticProps, - getServerSideProps, - getStaticPaths - } = require(${stringifiedAbsolutePagePath}) - - const buildManifest = self.__BUILD_MANIFEST - const reactLoadableManifest = self.__REACT_LOADABLE_MANIFEST - const rscManifest = self.__RSC_MANIFEST - - if (typeof Page !== 'function') { - throw new Error('Your page must export a \`default\` component') - } - - const Component = Page - - async function render(request) { - const url = request.nextUrl - const { pathname, searchParams } = url - const query = Object.fromEntries(searchParams) - - // Preflight request - if (request.method === 'HEAD') { - return new Response('OK.', { - headers: { 'x-middleware-ssr': '1' } - }) - } - - const renderServerComponentData = ${ - isServerComponent ? `query.__flight__ !== undefined` : 'false' - } - delete query.__flight__ - - const req = { url: pathname } - const renderOpts = { - Component, - pageConfig: config || {}, - // Locales are not supported yet. - // locales: i18n?.locales, - // locale: detectedLocale, - // defaultLocale, - // domainLocales: i18n?.domains, - dev: process.env.NODE_ENV !== 'production', - App, - Document, - buildManifest, - getStaticProps, - getServerSideProps, - getStaticPaths, - reactLoadableManifest, - buildId: ${JSON.stringify(buildId)}, - assetPrefix: ${JSON.stringify(assetPrefix || '')}, - env: process.env, - basePath: ${JSON.stringify(basePath || '')}, - supportsDynamicHTML: true, - concurrentFeatures: true, - renderServerComponentData, - serverComponentManifest: ${ - isServerComponent ? 'rscManifest' : 'null' - }, - } - - const transformStream = new TransformStream() - const writer = transformStream.writable.getWriter() - const encoder = new TextEncoder() - let result - let renderError - let statusCode = 200 - try { - result = await renderToHTML( - req, - {}, - pathname, - query, - renderOpts - ) - } catch (err) { - renderError = err - statusCode = 500 - } - if (renderError) { - try { - const errorRes = { statusCode, err: renderError } - result = await renderToHTML( - req, - errorRes, - '/_error', - query, - { - ...renderOpts, - Component: errorMod.default, - getStaticProps: errorMod.getStaticProps, - getServerSideProps: errorMod.getServerSideProps, - getStaticPaths: errorMod.getStaticPaths, - } - ) - } catch (err) { - return new Response( - (err || 'An error occurred while rendering ' + pathname + '.').toString(), - { - status: 500, - headers: { 'x-middleware-ssr': '1' } - } - ) - } - } - - result.pipe({ - write: str => writer.write(encoder.encode(str)), - end: () => writer.close(), - // Not implemented: cork/uncork/on/removeListener - }) - - return new Response(transformStream.readable, { - headers: { 'x-middleware-ssr': '1' }, - status: statusCode - }) - } - - export default function rscMiddleware(opts) { - return adapter({ - ...opts, - handler: render - }) - } - ` + import { adapter } from 'next/dist/server/web/adapter' + import { RouterContext } from 'next/dist/shared/lib/router-context' + + import App from ${stringifiedAbsoluteAppPath} + import Document from ${stringifiedAbsoluteDocumentPath} + + import { getRender } from 'next/dist/build/webpack/loaders/next-middleware-ssr-loader/render' + + const pageMod = require(${stringifiedAbsolutePagePath}) + const errorMod = require(${stringifiedAbsolute500PagePath}) + + const buildManifest = self.__BUILD_MANIFEST + const reactLoadableManifest = self.__REACT_LOADABLE_MANIFEST + const rscManifest = self.__RSC_MANIFEST + + if (typeof pageMod.default !== 'function') { + throw new Error('Your page must export a \`default\` component') + } + + const render = getRender({ + App, + Document, + pageMod, + errorMod, + buildManifest, + reactLoadableManifest, + rscManifest, + isServerComponent: ${JSON.stringify(isServerComponent)}, + restRenderOpts: ${JSON.stringify(restRenderOpts)} + }) + + export default function rscMiddleware(opts) { + return adapter({ + ...opts, + handler: render + }) + }` return transformed } diff --git a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts new file mode 100644 index 000000000000000..122bd3217944180 --- /dev/null +++ b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts @@ -0,0 +1,133 @@ +import { NextRequest } from '../../../../server/web/spec-extension/request' +import { renderToHTML } from '../../../../server/web/render' +import RenderResult from '../../../../server/render-result' + +export function getRender({ + App, + Document, + pageMod, + errorMod, + rscManifest, + buildManifest, + reactLoadableManifest, + isServerComponent, + restRenderOpts, +}: { + App: any + Document: any + pageMod: any + errorMod: any + rscManifest: object + buildManifest: any + reactLoadableManifest: any + isServerComponent: boolean + restRenderOpts: any +}) { + return async function render(request: NextRequest) { + const url = request.nextUrl + const { pathname, searchParams } = url + + const query = Object.fromEntries(searchParams) + + // Preflight request + if (request.method === 'HEAD') { + return new Response('OK.', { + headers: { 'x-middleware-ssr': '1' }, + }) + } + + const renderServerComponentData = isServerComponent + ? query.__flight__ !== undefined + : false + delete query.__flight__ + + const req = { url: pathname } + const renderOpts = { + ...restRenderOpts, + // Locales are not supported yet. + // locales: i18n?.locales, + // locale: detectedLocale, + // defaultLocale, + // domainLocales: i18n?.domains, + dev: process.env.NODE_ENV !== 'production', + App, + Document, + buildManifest, + Component: pageMod.default, + pageConfig: pageMod.config || {}, + getStaticProps: pageMod.getStaticProps, + getServerSideProps: pageMod.getServerSideProps, + getStaticPaths: pageMod.getStaticPaths, + reactLoadableManifest, + env: process.env, + supportsDynamicHTML: true, + concurrentFeatures: true, + renderServerComponentData, + serverComponentManifest: isServerComponent ? rscManifest : null, + ComponentMod: null, + } + + const transformStream = new TransformStream() + const writer = transformStream.writable.getWriter() + const encoder = new TextEncoder() + + let result: RenderResult | null + try { + result = await renderToHTML( + req as any, + {} as any, + pathname, + query, + renderOpts + ) + } catch (err: any) { + const errorRes = { statusCode: 500, err } + try { + result = await renderToHTML( + req as any, + errorRes as any, + '/_error', + query, + { + ...renderOpts, + Component: errorMod.default, + getStaticProps: errorMod.getStaticProps, + getServerSideProps: errorMod.getServerSideProps, + getStaticPaths: errorMod.getStaticPaths, + } + ) + } catch (err2: any) { + return new Response( + ( + err2 || 'An error occurred while rendering ' + pathname + '.' + ).toString(), + { + status: 500, + headers: { 'x-middleware-ssr': '1' }, + } + ) + } + } + + if (!result) { + return new Response( + 'An error occurred while rendering ' + pathname + '.', + { + status: 500, + headers: { 'x-middleware-ssr': '1' }, + } + ) + } + + result.pipe({ + write: (str: string) => writer.write(encoder.encode(str)), + end: () => writer.close(), + // Not implemented: cork/uncork/on/removeListener + } as any) + + return new Response(transformStream.readable, { + headers: { 'x-middleware-ssr': '1' }, + status: 200, + }) + } +} diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts index 3887dace73edac3..f4150fcba925dab 100644 --- a/packages/next/server/dev/hot-reloader.ts +++ b/packages/next/server/dev/hot-reloader.ts @@ -529,11 +529,17 @@ export default class HotReloader { absoluteAppPath: this.pagesMapping['/_app'], absoluteDocumentPath: this.pagesMapping['/_document'], absoluteErrorPath: this.pagesMapping['/_error'], + absolute404Path: this.pagesMapping['/404'] || '', absolutePagePath, isServerComponent, buildId: this.buildId, basePath: this.config.basePath, assetPrefix: this.config.assetPrefix, + generateEtags: this.config.generateEtags, + poweredByHeader: this.config.poweredByHeader, + canonicalBase: this.config.amp.canonicalBase, + i18n: this.config.i18n, + previewProps: this.previewProps, } as any)}!`, isServer: false, isServerWeb: true,