diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts
index b3b3e394abd69..13b4d2ebc308e 100644
--- a/packages/next/build/webpack-config.ts
+++ b/packages/next/build/webpack-config.ts
@@ -361,11 +361,16 @@ export default async function getBaseWebpackConfig(
const hasReactRoot: boolean =
config.experimental.reactRoot || hasReact18 || isReactExperimental
- if (config.experimental.reactRoot && !(hasReact18 || isReactExperimental)) {
+ // Only inform during one of the builds
+ if (
+ !isServer &&
+ config.experimental.reactRoot &&
+ !(hasReact18 || isReactExperimental)
+ ) {
// It's fine to only mention React 18 here as we don't recommend people to try experimental.
Log.warn('You have to use React 18 to use `experimental.reactRoot`.')
}
- if (config.experimental.concurrentFeatures && !hasReactRoot) {
+ if (!isServer && config.experimental.concurrentFeatures && !hasReactRoot) {
throw new Error(
'`experimental.concurrentFeatures` requires `experimental.reactRoot` to be enabled along with React 18.'
)
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 562009149e308..0cfc7305c7339 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
@@ -1,7 +1,33 @@
import loaderUtils from 'next/dist/compiled/loader-utils'
import { getStringifiedAbsolutePath } from './utils'
-export default function middlewareRSCLoader(this: any) {
+const fallbackDocumentPage = `
+import { Html, Head, Main, NextScript } from 'next/document'
+
+function Document() {
+ return (
+ createElement(Html, null,
+ createElement(Head),
+ createElement('body', null,
+ createElement(Main),
+ createElement(NextScript),
+ )
+ )
+ )
+}
+`
+
+function hasModule(path: string) {
+ let has
+ try {
+ has = !!require.resolve(path)
+ } catch (_) {
+ has = false
+ }
+ return has
+}
+
+export default async function middlewareRSCLoader(this: any) {
const {
absolutePagePath,
basePath,
@@ -22,6 +48,21 @@ export default function middlewareRSCLoader(this: any) {
'./pages/_app'
)
+ const hasProvidedAppPage = hasModule(JSON.parse(stringifiedAbsoluteAppPath))
+ const hasProvidedDocumentPage = hasModule(
+ JSON.parse(stringifiedAbsoluteDocumentPath)
+ )
+
+ let appDefinition = `const App = require(${
+ hasProvidedAppPage
+ ? stringifiedAbsoluteAppPath
+ : JSON.stringify('next/dist/pages/_app')
+ }).default`
+
+ let documentDefinition = hasProvidedDocumentPage
+ ? `const Document = require(${stringifiedAbsoluteDocumentPath}).default`
+ : fallbackDocumentPage
+
const transformed = `
import { adapter } from 'next/dist/server/web/adapter'
@@ -38,30 +79,35 @@ export default function middlewareRSCLoader(this: any) {
: ''
}
- var {
+ ${documentDefinition}
+ ${appDefinition}
+
+ const {
default: Page,
config,
getStaticProps,
getServerSideProps,
getStaticPaths
} = require(${stringifiedAbsolutePagePath})
- var Document = require(${stringifiedAbsoluteDocumentPath}).default
- var App = require(${stringifiedAbsoluteAppPath}).default
const buildManifest = self.__BUILD_MANIFEST
const reactLoadableManifest = self.__REACT_LOADABLE_MANIFEST
const rscManifest = self._middleware_rsc_manifest
if (typeof Page !== 'function') {
- throw new Error('Your page must export a \`default\` component');
+ throw new Error('Your page must export a \`default\` component')
+ }
+
+ function renderError(err, status) {
+ return new Response(err.toString(), {status})
}
- function wrapReadable (readable) {
- var encoder = new TextEncoder()
- var transformStream = new TransformStream()
- var writer = transformStream.writable.getWriter()
- var reader = readable.getReader()
- var process = () => {
+ function wrapReadable(readable) {
+ const encoder = new TextEncoder()
+ const transformStream = new TransformStream()
+ const writer = transformStream.writable.getWriter()
+ const reader = readable.getReader()
+ const process = () => {
reader.read().then(({ done, value }) => {
if (!done) {
writer.write(typeof value === 'string' ? encoder.encode(value) : value)
@@ -82,7 +128,7 @@ export default function middlewareRSCLoader(this: any) {
let responseCache
const FlightWrapper = props => {
- var response = responseCache
+ let response = responseCache
if (!response) {
responseCache = response = createFromReadableStream(renderFlight(props))
}
@@ -103,6 +149,11 @@ export default function middlewareRSCLoader(this: any) {
const url = request.nextUrl
const query = Object.fromEntries(url.searchParams)
+ if (Document.getInitialProps) {
+ const err = new Error('Document.getInitialProps is not supported with server components, please remove it from pages/_document')
+ return renderError(err, 500)
+ }
+
// Preflight request
if (request.method === 'HEAD') {
return new Response('OK.', {
diff --git a/test/integration/react-rsc-basic/app/pages/_app.js b/test/integration/react-rsc-basic/app/pages/_app.js
deleted file mode 100644
index eee8821812081..0000000000000
--- a/test/integration/react-rsc-basic/app/pages/_app.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import '../styles.css'
-
-function App({ Component, pageProps }) {
- return