diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index 3d0047efc9bd..1ac79e357ed3 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -39,11 +39,9 @@ export function createPagesMapping( { isDev, hasServerComponents, - globalRuntime, }: { isDev: boolean hasServerComponents: boolean - globalRuntime?: 'nodejs' | 'edge' } ): PagesMapping { const previousPages: PagesMapping = {} @@ -83,7 +81,6 @@ export function createPagesMapping( // we alias these in development and allow webpack to // allow falling back to the correct source file so // that HMR can work properly when a file is added/removed - const documentPage = `_document${globalRuntime ? '-concurrent' : ''}` if (isDev) { pages['/_app'] = `${PAGES_DIR_ALIAS}/_app` pages['/_error'] = `${PAGES_DIR_ALIAS}/_error` @@ -91,8 +88,7 @@ export function createPagesMapping( } else { pages['/_app'] = pages['/_app'] || 'next/dist/pages/_app' pages['/_error'] = pages['/_error'] || 'next/dist/pages/_error' - pages['/_document'] = - pages['/_document'] || `next/dist/pages/${documentPage}` + pages['/_document'] = pages['/_document'] || `next/dist/pages/_document` } return pages } diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 25cf163cb6e4..b0952a2b2767 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -298,7 +298,6 @@ export default async function build( createPagesMapping(pagePaths, config.pageExtensions, { isDev: false, hasServerComponents, - globalRuntime: runtime, }) ) diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 813dbfbdae56..300857be7bd0 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -558,9 +558,7 @@ export default async function getBaseWebpackConfig( prev.push(path.join(pagesDir, `_document.${ext}`)) return prev }, [] as string[]), - `next/dist/pages/_document${ - hasConcurrentFeatures ? '-concurrent' : '' - }.js`, + `next/dist/pages/_document.js`, ] } diff --git a/packages/next/pages/_document-concurrent.tsx b/packages/next/pages/_document-concurrent.tsx deleted file mode 100644 index 8275efd253fe..000000000000 --- a/packages/next/pages/_document-concurrent.tsx +++ /dev/null @@ -1,16 +0,0 @@ -// Default Document for streaming features - -import React from 'react' -import { Html, Head, Main, NextScript } from './_document' - -export default function Document() { - return ( - - - -
- - - - ) -} diff --git a/packages/next/pages/_document.tsx b/packages/next/pages/_document.tsx index 15ad248f3c47..305211250ade 100644 --- a/packages/next/pages/_document.tsx +++ b/packages/next/pages/_document.tsx @@ -184,6 +184,21 @@ export default class Document

extends Component { } } +// Add a speical property to the built-in `Document` component so later we can +// identify if a user customized `Document` is used or not. +;(Document as any).__next_internal_document = + function InternalFunctionDocument() { + return ( + + + +

+ + + + ) + } + export function Html( props: React.DetailedHTMLProps< React.HtmlHTMLAttributes, diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 2168e8d163d5..56f0dedf8a1d 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1176,6 +1176,13 @@ export default abstract class Server { if (opts.supportsDynamicHTML === true) { const isBotRequest = isBot(req.headers['user-agent'] || '') + const isSupportedDocument = + typeof components.Document?.getInitialProps !== 'function' || + // When concurrent features is enabled, the built-in `Document` + // component also supports dynamic HTML. + (this.renderOpts.reactRoot && + !!(components.Document as any)?.__next_internal_document) + // Disable dynamic HTML in cases that we know it won't be generated, // so that we can continue generating a cache key when possible. opts.supportsDynamicHTML = @@ -1183,7 +1190,7 @@ export default abstract class Server { !isLikeServerless && !isBotRequest && !query.amp && - typeof components.Document?.getInitialProps !== 'function' + isSupportedDocument } const defaultLocale = isSSG diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts index d5e56dbf4f7b..bcd58454ab57 100644 --- a/packages/next/server/dev/hot-reloader.ts +++ b/packages/next/server/dev/hot-reloader.ts @@ -321,7 +321,6 @@ export default class HotReloader { this.config.pageExtensions, { isDev: true, - globalRuntime: this.runtime, hasServerComponents: this.hasServerComponents, } ) diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index 66ad197086af..15c58725143e 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -949,9 +949,7 @@ export default class DevServer extends Server { // Build the error page to ensure the fallback is built too. // TODO: See if this can be moved into hotReloader or removed. await this.hotReloader!.ensurePage('/_error') - return await loadDefaultErrorComponents(this.distDir, { - hasConcurrentFeatures: !!this.renderOpts.runtime, - }) + return await loadDefaultErrorComponents(this.distDir) } protected setImmutableAssetCacheControl(res: BaseNextResponse): void { diff --git a/packages/next/server/load-components.ts b/packages/next/server/load-components.ts index 8ccafc60a75d..ae247d9ea66f 100644 --- a/packages/next/server/load-components.ts +++ b/packages/next/server/load-components.ts @@ -39,14 +39,8 @@ export type LoadComponentsReturnType = { AppMod: any } -export async function loadDefaultErrorComponents( - distDir: string, - { hasConcurrentFeatures }: { hasConcurrentFeatures: boolean } -) { - const Document = interopDefault( - require(`next/dist/pages/_document` + - (hasConcurrentFeatures ? '-concurrent' : '')) - ) +export async function loadDefaultErrorComponents(distDir: string) { + const Document = interopDefault(require('next/dist/pages/_document')) const AppMod = require('next/dist/pages/_app') const App = interopDefault(AppMod) const ComponentMod = require('next/dist/pages/_error') diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index 679fcde4f62b..8b9eae2fbb63 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -431,7 +431,6 @@ export async function renderToHTML( dev = false, ampPath = '', App, - Document, pageConfig = {}, buildManifest, fontManifest, @@ -457,6 +456,7 @@ export async function renderToHTML( const hasConcurrentFeatures = !!runtime + let Document = renderOpts.Document const OriginalComponent = renderOpts.Component // We don't need to opt-into the flight inlining logic if the page isn't a RSC. @@ -1234,14 +1234,30 @@ export async function renderToHTML( */ const generateStaticHTML = supportsDynamicHTML !== true const renderDocument = async () => { + // For `Document`, there are two cases that we don't support: + // 1. Using `Document.getInitialProps` in the Edge runtime. + // 2. Using the class component `Document` with concurrent features. + + const builtinDocument = (Document as any).__next_internal_document as + | typeof Document + | undefined + if (runtime === 'edge' && Document.getInitialProps) { - // In the Edge runtime, Document.getInitialProps isn't supported. - throw new Error( - '`getInitialProps` in Document component is not supported with the Edge Runtime.' - ) + // In the Edge runtime, `Document.getInitialProps` isn't supported. + // We throw an error here if it's customized. + if (!builtinDocument) { + throw new Error( + '`getInitialProps` in Document component is not supported with the Edge Runtime.' + ) + } + } + + // We make it a function component to enable streaming. + if (hasConcurrentFeatures && builtinDocument) { + Document = builtinDocument } - if (!runtime && Document.getInitialProps) { + if (!hasConcurrentFeatures && Document.getInitialProps) { const renderPage: RenderPage = ( options: ComponentsEnhancer = {} ): RenderPageResult | Promise => {