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 => {