diff --git a/errors/fast-refresh-reload.md b/errors/fast-refresh-reload.md new file mode 100644 index 000000000000000..14023d575046c5e --- /dev/null +++ b/errors/fast-refresh-reload.md @@ -0,0 +1,17 @@ +# Fast Refresh had to perform full reload + +#### Why This Error Occurred + +Fast Refresh had to perform a full reload when you edited a file. It may be because: + +- The file you're editing might have other exports in addition to a React component. +- Your React component is an anonymous function. + +#### Possible Ways to Fix It + +- Move your other exports to a separate file. +- Use a named function for your React component. + +### Useful Links + +[Fast Refresh documentation](https://nextjs.org/docs/basic-features/fast-refresh) diff --git a/errors/manifest.json b/errors/manifest.json index 21e8f8478245360..f2ed0ae1656e749 100644 --- a/errors/manifest.json +++ b/errors/manifest.json @@ -769,6 +769,10 @@ { "title": "app-dir-dynamic-href", "path": "/errors/app-dir-dynamic-href.md" + }, + { + "title": "fast-refresh-reload", + "path": "/errors/fast-refresh-reload.md" } ] } 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 63d0256ac3a6632..aafba5fa4db5a37 100644 --- a/packages/next/client/dev/error-overlay/hot-dev-client.js +++ b/packages/next/client/dev/error-overlay/hot-dev-client.js @@ -398,6 +398,7 @@ function performFullReload(err) { JSON.stringify({ event: 'client-full-reload', stackTrace, + hadRuntimeError: !!hadRuntimeError, }) ) diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts index baa09430552bb5d..940ab3e35e6947a 100644 --- a/packages/next/server/dev/hot-reloader.ts +++ b/packages/next/server/dev/hot-reloader.ts @@ -357,16 +357,33 @@ export default class HotReloader { break } case 'client-full-reload': { + const { event, stackTrace, hadRuntimeError } = payload + traceChild = { - name: payload.event, - attrs: { stackTrace: payload.stackTrace ?? '' }, + name: event, + attrs: { stackTrace: stackTrace ?? '' }, + } + + if (hadRuntimeError) { + Log.warn( + `Fast Refresh had to perform a full reload due to a runtime error.` + ) + break } + + let fileMessage = '' + if (stackTrace) { + const file = /Aborted because (.+) is not accepted/.exec( + stackTrace + )?.[1] + if (file) { + fileMessage = ` when ${file} changed` + } + } + Log.warn( - 'Fast Refresh had to perform a full reload. Read more: https://nextjs.org/docs/basic-features/fast-refresh#how-it-works' + `Fast Refresh had to perform a full reload${fileMessage}. Read more: https://nextjs.org/docs/messages/fast-refresh-reload` ) - if (payload.stackTrace) { - console.warn(payload.stackTrace) - } break } default: { diff --git a/test/development/basic/hmr.test.ts b/test/development/basic/hmr.test.ts index 4bfd3ab66c1c364..13982bd06e9c1f1 100644 --- a/test/development/basic/hmr.test.ts +++ b/test/development/basic/hmr.test.ts @@ -778,10 +778,11 @@ describe('basic HMR', () => { next.appPort, `/hmr/anonymous-page-function` ) + const cliWarning = + 'Fast Refresh had to perform a full reload when ./pages/hmr/anonymous-page-function.js changed. Read more: https://nextjs.org/docs/messages/fast-refresh-reload' + expect(await browser.elementByCss('p').text()).toBe('hello world') - expect(next.cliOutput.slice(start)).not.toContain( - 'Fast Refresh had to perform a full reload. Read more: https://nextjs.org/docs/basic-features/fast-refresh#how-it-works' - ) + expect(next.cliOutput.slice(start)).not.toContain(cliWarning) const currentFileContent = await next.readFile( './pages/hmr/anonymous-page-function.js' @@ -799,13 +800,8 @@ describe('basic HMR', () => { 'hello world!!!' ) - // CLI warning and stacktrace - expect(next.cliOutput.slice(start)).toContain( - 'Fast Refresh had to perform a full reload. Read more: https://nextjs.org/docs/basic-features/fast-refresh#how-it-works' - ) - expect(next.cliOutput.slice(start)).toContain( - 'Error: Aborted because ./pages/hmr/anonymous-page-function.js is not accepted' - ) + // CLI warning + expect(next.cliOutput.slice(start)).toContain(cliWarning) // Browser warning const browserLogs = await browser.log() @@ -821,13 +817,14 @@ describe('basic HMR', () => { it('should warn about full reload in cli output - runtime-error', async () => { const start = next.cliOutput.length const browser = await webdriver(next.appPort, `/hmr/runtime-error`) + const cliWarning = + 'Fast Refresh had to perform a full reload due to a runtime error.' + await check( () => getRedboxHeader(browser), /ReferenceError: whoops is not defined/ ) - expect(next.cliOutput.slice(start)).not.toContain( - 'Fast Refresh had to perform a full reload. Read more: https://nextjs.org/docs/basic-features/fast-refresh#how-it-works' - ) + expect(next.cliOutput.slice(start)).not.toContain(cliWarning) const currentFileContent = await next.readFile( './pages/hmr/runtime-error.js' @@ -842,13 +839,8 @@ describe('basic HMR', () => { 'whoops' ) - // CLI warning and stacktrace - expect(next.cliOutput.slice(start)).toContain( - 'Fast Refresh had to perform a full reload. Read more: https://nextjs.org/docs/basic-features/fast-refresh#how-it-works' - ) - expect(next.cliOutput.slice(start)).not.toContain( - 'Error: Aborted because ./pages/runtime-error.js is not accepted' - ) + // CLI warning + expect(next.cliOutput.slice(start)).toContain(cliWarning) // Browser warning const browserLogs = await browser.log()