-
Notifications
You must be signed in to change notification settings - Fork 30.7k
Description
Link to the code that reproduces this issue
https://github.com/gurkerl83/next-turbo-dynamic-import
To Reproduce
The following reproducer was create for another ticket. The HMR problem is observable even in this demonstrator, so I hope this is OK for reuse.
- Clone the reproduction repository:
git clone https://github.com/gurkerl83/next-turbo-dynamic-import.git-
The reproduction case was originally created to address a different issue. To use it for this problem, please comment out both the import and render of the ComponentNamedImportWithIssue component.
-
Install dependencies using pnpm:
pnpm install- Run the development server with Turbopack:
pnpm dev:turbo- Go to the browser, Open Dev Tools, Observe the console warnings and errors upon startup:
[HMR] Invalid message: {"action":"appIsrManifest","data":{}}
TypeError: Cannot read properties of undefined (reading 'pathname')
at http://localhost:3000/_next/static/chunks/...
Current vs. Expected behavior
Current behavior: When starting the application with Turbopack in development mode, a TypeError occurs due to window.next.router being undefined during HMR events, specifically during the appIsrManifest action. This results in console warnings and may disrupt the development workflow.
Expected behavior: The application should start without any TypeError or HMR warnings. Hot Module Replacement (HMR) events should not cause errors even if window.next.router is not yet initialized.
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 23.5.0: Wed May 1 20:17:33 PDT 2024; root:xnu-10063.121.3~5/RELEASE_ARM64_T6031
Available memory (MB): 65536
Available CPU cores: 16
Binaries:
Node: 22.6.0
npm: 10.8.2
Yarn: N/A
pnpm: 9.11.0
Relevant Packages:
next: 15.0.2-canary.9 // Latest available version.
react: 19.0.0-rc-cae764ce-20241025
react-dom: 19.0.0-rc-cae764ce-20241025
typescript: 5.6.3
Next.js Config:
output: N/AWhich area(s) are affected? (Select all that apply)
Pages Router, Turbopack
Which stage(s) are affected? (Select all that apply)
next dev (local)
Additional context
In the Next.js source code, there are two distinct hot-reloader-client implementations: one for the Page Router and another for the App Router.
The Page Router variant includes a custom event handler, customHmrEventHandler, which is responsible for handling specific hot-reload events. This functionality is implemented in the following code snippet to address the issue effectively.
packages/next/src/client/dev/hot-middleware-client.ts
Note:
This issue may not be detectable in the App Router because the event handler in question is not utilized there. The reproduction case specifically relies on the Page Router.
import type {
NextRouter,
PrivateRouteInfo,
} from '../../shared/lib/router/router'
import connect from '../components/react-dev-overlay/pages/hot-reloader-client'
import { sendMessage } from '../components/react-dev-overlay/pages/websocket'
// Define a local type for the window.next object
interface NextWindow {
next?: {
router?: NextRouter & {
components: { [pathname: string]: PrivateRouteInfo }
}
}
__nextDevClientId?: string
location: Location
}
declare const window: NextWindow
let reloading = false
export default (mode: 'webpack' | 'turbopack') => {
const devClient = connect(mode)
devClient.subscribeToHmrEvent((obj) => {
if (reloading) return
// Retrieve the router if it's available
const router = window.next?.router
// Determine if we're on an error page or the router is not initialized
const isOnErrorPage =
!router || router.pathname === '/404' || router.pathname === '/_error'
switch (obj.action) {
case 'reloadPage': {
sendMessage(
JSON.stringify({
event: 'client-reload-page',
clientId: window.__nextDevClientId,
})
)
reloading = true
return window.location.reload()
}
case 'removedPage': {
const [page] = obj.data
// Check if the removed page is the current page
const isCurrentPage = page === router?.pathname
if (isCurrentPage || isOnErrorPage) {
sendMessage(
JSON.stringify({
event: 'client-removed-page',
clientId: window.__nextDevClientId,
page,
})
)
return window.location.reload()
}
return
}
case 'addedPage': {
const [page] = obj.data
// Check if the added page is the current page
const isCurrentPage = page === router?.pathname
// Check if the page component is not yet loaded
const isPageNotLoaded =
typeof router?.components?.[page] === 'undefined'
if ((isCurrentPage && isPageNotLoaded) || isOnErrorPage) {
sendMessage(
JSON.stringify({
event: 'client-added-page',
clientId: window.__nextDevClientId,
page,
})
)
return window.location.reload()
}
return
}
case 'serverError':
case 'devPagesManifestUpdate':
case 'appIsrManifest':
case 'building':
case 'finishBuilding': {
return
}
default: {
throw new Error('Unexpected action ' + obj.action)
}
}
})
return devClient
}