diff --git a/packages/next/client/app-index.tsx b/packages/next/client/app-index.tsx
index bb5f49a1304a..dd11f6a8d4b0 100644
--- a/packages/next/client/app-index.tsx
+++ b/packages/next/client/app-index.tsx
@@ -212,19 +212,6 @@ function ServerRoot({ cacheKey }: { cacheKey: string }) {
return root
}
-function ErrorOverlay({
- children,
-}: React.PropsWithChildren<{}>): React.ReactElement {
- if (process.env.NODE_ENV === 'production') {
- return <>{children}>
- } else {
- const {
- ReactDevOverlay,
- } = require('next/dist/compiled/@next/react-dev-overlay/dist/client')
- return {children}
- }
-}
-
function Root({ children }: React.PropsWithChildren<{}>): React.ReactElement {
if (process.env.__NEXT_TEST_MODE) {
// eslint-disable-next-line react-hooks/rules-of-hooks
@@ -247,12 +234,10 @@ function RSCComponent() {
export function hydrate() {
renderReactElement(appElement!, () => (
-
-
-
-
-
-
-
+
+
+
+
+
))
}
diff --git a/packages/next/client/components/app-router.client.tsx b/packages/next/client/components/app-router.client.tsx
index 913bbfde7bee..7dc6e8bffd93 100644
--- a/packages/next/client/components/app-router.client.tsx
+++ b/packages/next/client/components/app-router.client.tsx
@@ -36,6 +36,19 @@ export function fetchServerResponse(
return createFromFetch(fetchFlight(url, flightRouterStateData))
}
+function ErrorOverlay({
+ children,
+}: React.PropsWithChildren<{}>): React.ReactElement {
+ if (process.env.NODE_ENV === 'production') {
+ return <>{children}>
+ } else {
+ const {
+ ReactDevOverlay,
+ } = require('next/dist/compiled/@next/react-dev-overlay/dist/client')
+ return {children}
+ }
+}
+
// TODO: move this back into AppRouter
let initialParallelRoutes: CacheNode['parallelRoutes'] =
typeof window === 'undefined' ? null! : new Map()
@@ -248,7 +261,7 @@ export default function AppRouter({
url: canonicalUrl,
}}
>
- {cache.subTreeData}
+ {cache.subTreeData}
{hotReloader}
diff --git a/packages/next/client/components/hot-reloader.client.tsx b/packages/next/client/components/hot-reloader.client.tsx
index 30cc653b12c7..a431c723d598 100644
--- a/packages/next/client/components/hot-reloader.client.tsx
+++ b/packages/next/client/components/hot-reloader.client.tsx
@@ -1,7 +1,15 @@
-import { useCallback, useContext, useEffect, useRef } from 'react'
+import {
+ useCallback,
+ useContext,
+ useEffect,
+ useRef,
+ // @ts-expect-error TODO: startTransition exists
+ startTransition,
+} from 'react'
import { FullAppTreeContext } from '../../shared/lib/app-router-context'
import {
register,
+ unregister,
onBuildError,
onBuildOk,
onRefresh,
@@ -305,7 +313,15 @@ function processMessage(
clientId: __nextDevClientId,
})
)
- return router.reload()
+ if (hadRuntimeError) {
+ return window.location.reload()
+ }
+ startTransition(() => {
+ router.reload()
+ onRefresh()
+ })
+
+ return
}
case 'reloadPage': {
sendMessage(
@@ -393,6 +409,16 @@ export default function HotReload({ assetPrefix }: { assetPrefix: string }) {
useEffect(() => {
register()
+ const onError = () => {
+ hadRuntimeError = true
+ }
+ window.addEventListener('error', onError)
+ window.addEventListener('unhandledrejection', onError)
+ return () => {
+ unregister()
+ window.removeEventListener('error', onError)
+ window.removeEventListener('unhandledrejection', onError)
+ }
}, [])
useEffect(() => {
diff --git a/packages/next/client/dev/error-overlay/hot-dev-client.js b/packages/next/client/dev/error-overlay/hot-dev-client.js
index d6b0918952bc..6153a6f848c1 100644
--- a/packages/next/client/dev/error-overlay/hot-dev-client.js
+++ b/packages/next/client/dev/error-overlay/hot-dev-client.js
@@ -260,6 +260,16 @@ function processMessage(e) {
)
return handleSuccess()
}
+
+ case 'serverComponentChanges': {
+ sendMessage(
+ JSON.stringify({
+ event: 'server-component-reload-page',
+ clientId: window.__nextDevClientId,
+ })
+ )
+ return window.location.reload()
+ }
default: {
if (customHmrEventHandler) {
customHmrEventHandler(obj)
diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js
index fa8f54b68e71..785ef9efcf37 100644
--- a/packages/next/taskfile.js
+++ b/packages/next/taskfile.js
@@ -1982,6 +1982,11 @@ export default async function (task) {
opts
)
await task.watch('server/**/*.+(wasm)', 'server_wasm', opts)
+ await task.watch(
+ '../react-dev-overlay/dist/**/*.js',
+ 'ncc_next__react_dev_overlay',
+ opts
+ )
}
export async function shared(task, opts) {
diff --git a/packages/react-dev-overlay/src/internal/ErrorBoundary.tsx b/packages/react-dev-overlay/src/internal/ErrorBoundary.tsx
index 4c2267237dee..b2a1a6f0a984 100644
--- a/packages/react-dev-overlay/src/internal/ErrorBoundary.tsx
+++ b/packages/react-dev-overlay/src/internal/ErrorBoundary.tsx
@@ -2,6 +2,8 @@ import React from 'react'
type ErrorBoundaryProps = {
onError: (error: Error, componentStack: string | null) => void
+ globalOverlay?: boolean
+ isMounted?: boolean
}
type ErrorBoundaryState = { error: Error | null }
@@ -10,7 +12,6 @@ class ErrorBoundary extends React.PureComponent<
ErrorBoundaryState
> {
state = { error: null }
-
componentDidCatch(
error: Error,
// Loosely typed because it depends on the React version and was
@@ -18,14 +19,26 @@ class ErrorBoundary extends React.PureComponent<
errorInfo?: { componentStack?: string | null }
) {
this.props.onError(error, errorInfo?.componentStack || null)
- this.setState({ error })
+ if (!this.props.globalOverlay) {
+ this.setState({ error })
+ }
}
render() {
- return this.state.error
- ? // The component has to be unmounted or else it would continue to error
- null
- : this.props.children
+ // The component has to be unmounted or else it would continue to error
+ return this.state.error ||
+ (this.props.globalOverlay && this.props.isMounted) ? (
+ // When the overlay is global for the application and it wraps a component rendering ``
+ // we have to render the html shell otherwise the shadow root will not be able to attach
+ this.props.globalOverlay ? (
+
+
+
+
+ ) : null
+ ) : (
+ this.props.children
+ )
}
}
diff --git a/packages/react-dev-overlay/src/internal/ReactDevOverlay.tsx b/packages/react-dev-overlay/src/internal/ReactDevOverlay.tsx
index fe2fe056ca2c..d125ccc93bf1 100644
--- a/packages/react-dev-overlay/src/internal/ReactDevOverlay.tsx
+++ b/packages/react-dev-overlay/src/internal/ReactDevOverlay.tsx
@@ -31,7 +31,13 @@ function reducer(state: OverlayState, ev: Bus.BusEvent): OverlayState {
return {
...state,
nextId: state.nextId + 1,
- errors: [...state.errors, { id: state.nextId, event: ev }],
+ errors: [
+ ...state.errors.filter((err) => {
+ // Filter out duplicate errors
+ return err.event.reason !== ev.reason
+ }),
+ { id: state.nextId, event: ev },
+ ],
}
}
default: {
@@ -82,7 +88,11 @@ const ReactDevOverlay: React.FunctionComponent = function ReactDevOverlay({
return (
-
+
{children ?? null}
{isMounted ? (