diff --git a/packages/next/src/client/components/react-dev-overlay/app/hot-reloader-client.tsx b/packages/next/src/client/components/react-dev-overlay/app/hot-reloader-client.tsx index 773fe49037637..b6a327657ee4d 100644 --- a/packages/next/src/client/components/react-dev-overlay/app/hot-reloader-client.tsx +++ b/packages/next/src/client/components/react-dev-overlay/app/hot-reloader-client.tsx @@ -24,10 +24,8 @@ import { } from './error-overlay-reducer' import { parseStack } from '../internal/helpers/parseStack' import ReactDevOverlay from './ReactDevOverlay' -import { - RuntimeErrorHandler, - useErrorHandler, -} from '../internal/helpers/use-error-handler' +import { useErrorHandler } from '../internal/helpers/use-error-handler' +import { RuntimeErrorHandler } from '../internal/helpers/runtime-error-handler' import { useSendMessage, useTurbopack, @@ -42,6 +40,7 @@ import type { TurbopackMsgToBrowser, } from '../../../../server/dev/hot-reloader-types' import { extractModulesFromTurbopackMessage } from '../../../../server/dev/extract-modules-from-turbopack-message' +import { REACT_REFRESH_FULL_RELOAD_FROM_ERROR } from '../../../dev/error-overlay/messages' interface Dispatcher { onBuildOk(): void @@ -184,9 +183,7 @@ function tryApplyUpdates( 'Fast Refresh requires at least one parent function component in your React tree.' ) } else if (RuntimeErrorHandler.hadRuntimeError) { - console.warn( - '[Fast Refresh] performing full reload because your application had an unrecoverable error' - ) + console.warn(REACT_REFRESH_FULL_RELOAD_FROM_ERROR) } performFullReload(err, sendMessage) return @@ -391,6 +388,10 @@ function processMessage( data: obj.data, }) dispatcher.onRefresh() + if (RuntimeErrorHandler.hadRuntimeError) { + console.warn(REACT_REFRESH_FULL_RELOAD_FROM_ERROR) + performFullReload(null, sendMessage) + } reportHmrLatency(sendMessage, updatedModules) break } diff --git a/packages/next/src/client/components/react-dev-overlay/internal/helpers/runtime-error-handler.ts b/packages/next/src/client/components/react-dev-overlay/internal/helpers/runtime-error-handler.ts new file mode 100644 index 0000000000000..36622efd1cb13 --- /dev/null +++ b/packages/next/src/client/components/react-dev-overlay/internal/helpers/runtime-error-handler.ts @@ -0,0 +1,3 @@ +export const RuntimeErrorHandler = { + hadRuntimeError: false, +} diff --git a/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-error-handler.ts b/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-error-handler.ts index 27a7a61f85f33..62f8c0b40a61e 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-error-handler.ts +++ b/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-error-handler.ts @@ -7,10 +7,6 @@ import { isNextRouterError } from '../../../is-next-router-error' export type ErrorHandler = (error: Error) => void -export const RuntimeErrorHandler = { - hadRuntimeError: false, -} - function isHydrationError(error: Error): boolean { return ( error.message.match(/(hydration|content does not match|did not match)/i) != diff --git a/packages/next/src/client/dev/error-overlay/hot-dev-client.ts b/packages/next/src/client/dev/error-overlay/hot-dev-client.ts index 9c8f63d223cb2..ccdb70f7ba73d 100644 --- a/packages/next/src/client/dev/error-overlay/hot-dev-client.ts +++ b/packages/next/src/client/dev/error-overlay/hot-dev-client.ts @@ -44,6 +44,8 @@ import type { TurbopackMsgToBrowser, } from '../../../server/dev/hot-reloader-types' import { extractModulesFromTurbopackMessage } from '../../../server/dev/extract-modules-from-turbopack-message' +import { RuntimeErrorHandler } from '../../components/react-dev-overlay/internal/helpers/runtime-error-handler' +import { REACT_REFRESH_FULL_RELOAD_FROM_ERROR } from './messages' // This alternative WebpackDevServer combines the functionality of: // https://github.com/webpack/webpack-dev-server/blob/webpack-1/client/index.js // https://github.com/webpack/webpack/blob/webpack-1/hot/dev-server.js @@ -339,6 +341,10 @@ function processMessage(obj: HMR_ACTION_TYPES) { data: obj.data, }) } + if (RuntimeErrorHandler.hadRuntimeError) { + console.warn(REACT_REFRESH_FULL_RELOAD_FROM_ERROR) + performFullReload(null) + } onRefresh() reportHmrLatency(updatedModules) break diff --git a/packages/next/src/client/dev/error-overlay/messages.ts b/packages/next/src/client/dev/error-overlay/messages.ts new file mode 100644 index 0000000000000..dacbfca069ef8 --- /dev/null +++ b/packages/next/src/client/dev/error-overlay/messages.ts @@ -0,0 +1,2 @@ +export const REACT_REFRESH_FULL_RELOAD_FROM_ERROR = + '[Fast Refresh] performing full reload because your application had an unrecoverable error' diff --git a/packages/next/src/server/dev/hot-reloader-turbopack.ts b/packages/next/src/server/dev/hot-reloader-turbopack.ts index 533f79252c271..1bcbc89aedbd5 100644 --- a/packages/next/src/server/dev/hot-reloader-turbopack.ts +++ b/packages/next/src/server/dev/hot-reloader-turbopack.ts @@ -71,6 +71,7 @@ import { getEntryKey, splitEntryKey, } from './turbopack/entry-key' +import { FAST_REFRESH_RUNTIME_RELOAD } from './messages' const wsServer = new ws.Server({ noServer: true }) const isTestMode = !!( @@ -542,6 +543,11 @@ export async function createHotReloaderTurbopack( case 'client-reload-page': // { clientId } case 'client-removed-page': // { page } case 'client-full-reload': // { stackTrace, hadRuntimeError } + const { hadRuntimeError } = parsedData + if (hadRuntimeError) { + Log.warn(FAST_REFRESH_RUNTIME_RELOAD) + } + break case 'client-added-page': // TODO break diff --git a/packages/next/src/server/dev/hot-reloader-webpack.ts b/packages/next/src/server/dev/hot-reloader-webpack.ts index 4250973e96974..65e650431c845 100644 --- a/packages/next/src/server/dev/hot-reloader-webpack.ts +++ b/packages/next/src/server/dev/hot-reloader-webpack.ts @@ -79,6 +79,7 @@ import { import type { HMR_ACTION_TYPES } from './hot-reloader-types' import type { WebpackError } from 'webpack' import { PAGE_TYPES } from '../../lib/page-types' +import { FAST_REFRESH_RUNTIME_RELOAD } from './messages' const MILLISECONDS_IN_NANOSECOND = 1_000_000 const isTestMode = !!( @@ -494,9 +495,7 @@ export default class HotReloaderWebpack implements NextJsHotReloaderInterface { } if (hadRuntimeError) { - Log.warn( - `Fast Refresh had to perform a full reload due to a runtime error.` - ) + Log.warn(FAST_REFRESH_RUNTIME_RELOAD) break } diff --git a/packages/next/src/server/dev/messages.ts b/packages/next/src/server/dev/messages.ts new file mode 100644 index 0000000000000..1bc11293d0de9 --- /dev/null +++ b/packages/next/src/server/dev/messages.ts @@ -0,0 +1,2 @@ +export const FAST_REFRESH_RUNTIME_RELOAD = + 'Fast Refresh had to perform a full reload due to a runtime error.' diff --git a/test/turbopack-tests-manifest.json b/test/turbopack-tests-manifest.json index cf5bf3c80af18..082cb00ebe2e7 100644 --- a/test/turbopack-tests-manifest.json +++ b/test/turbopack-tests-manifest.json @@ -1083,9 +1083,10 @@ "Error recovery app turbo render error not shown right after syntax error", "Error recovery app turbo server component can recover from a component error", "Error recovery app turbo server component can recover from syntax error", + "Error recovery app turbo stuck error", "Error recovery app turbo syntax > runtime error" ], - "failed": ["Error recovery app turbo stuck error"], + "failed": [], "pending": [ "Error recovery app default can recover from a event handler error", "Error recovery app default can recover from a syntax error without losing state",