Skip to content

Commit 447b416

Browse files
agustintsztanner
andauthored
Fix: HMR in multi-zone handling 🌱 (#59471)
### What? When running a [multi-zone](https://github.com/vercel/next.js/tree/canary/examples/with-zones) app in dev, guest app pages would infinitely reload if you change the basePath of the host app to the default one (omit basePath settings in next.config.js) (empty string `""` as per Next.js docs). ### Why? The HMR upgrade request would fail and get caught into a retry loop. In the multi-zone case, they fail because the upgrade request would be sent again for a request that had already been upgraded. This resulted in a "server.handleUpgrade() was called more than once with the same socket" error, causing the upgrade request to fail. Every time a retry occurred, the page would trigger a full refresh since certain HMR errors cause the browser to reload. ### How? This ensures the upgrade handler only responds to requests that match the configured basePath (considering when there is no basePath). Default basePath for Next.js applications it's an empty string `""`. Ref: https://nextjs.org/docs/app/api-reference/next-config-js/basePath Other fixes & updates related to the bug: - Updated test apps to avoid having issues regarding client & server mismatch for dates - Added default use case in e2e tests, where you have a default Next.js application where the basePath it's the default one and a guest app that it's being routed by the main one through Next.js rewrites. Closes NEXT-1797 Fixes #59161 Fixes #56615 Fixes #54454 --------- Co-authored-by: Zack Tanner <[email protected]>
1 parent 7e53e08 commit 447b416

File tree

19 files changed

+70
-90
lines changed

19 files changed

+70
-90
lines changed

‎packages/next/src/server/lib/router-server.ts‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
import { RedirectStatusCode } from '../../client/components/redirect-status-code'
3434
import { DevBundlerService } from './dev-bundler-service'
3535
import { type Span, trace } from '../../trace'
36+
import { ensureLeadingSlash } from '../../shared/lib/page-path/ensure-leading-slash'
3637

3738
const debug = setupDebug('next:router-server:main')
3839
const isNextFont = (pathname: string | null) =>
@@ -585,13 +586,15 @@ export async function initialize(opts: {
585586
})
586587

587588
if (opts.dev && developmentBundler && req.url) {
588-
const isHMRRequest = req.url.includes('/_next/webpack-hmr')
589+
const { basePath, assetPrefix } = config
590+
591+
const isHMRRequest = req.url.startsWith(
592+
ensureLeadingSlash(`${assetPrefix || basePath}/_next/webpack-hmr`)
593+
)
594+
589595
// only handle HMR requests if the basePath in the request
590596
// matches the basePath for the handler responding to the request
591-
const isRequestForCurrentBasepath =
592-
!config.basePath || pathHasPrefix(req.url, config.basePath)
593-
594-
if (isHMRRequest && isRequestForCurrentBasepath) {
597+
if (isHMRRequest) {
595598
return developmentBundler.hotReloader.onHMR(req, socket, head)
596599
}
597600
}

‎test/e2e/multi-zone/app/apps/first/next.config.js‎

Lines changed: 0 additions & 3 deletions
This file was deleted.

‎test/e2e/multi-zone/app/apps/first/pages/index.tsx‎

Lines changed: 0 additions & 11 deletions
This file was deleted.

‎test/e2e/multi-zone/app/apps/first/tsconfig.json‎

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
basePath: '/guest',
3+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"version": "0.0.1",
33
"private": true,
4-
"name": "first-app"
4+
"name": "guest-app"
55
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export default function Page() {
2-
return <p>hello from second app /another/[slug]</p>
2+
return <p>hello from guest app /another/[slug]</p>
33
}
44

55
export function getServerSideProps({ params }) {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export default function Page() {
2-
return <p>hello from first app /blog/[slug]</p>
2+
return <p>hello from guest app /blog/[slug]</p>
33
}
44

55
export function getServerSideProps({ params }) {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useEffect, useState } from 'react'
2+
3+
export default function Page() {
4+
const [date, setDate] = useState<Number>()
5+
6+
useEffect(() => {
7+
setDate(Date.now())
8+
}, [])
9+
10+
// this variable is edited by the test to verify HMR
11+
const editedContent = ''
12+
return (
13+
<>
14+
<p>hello from guest app</p>
15+
<div>{editedContent}</div>
16+
<div id="now">{`${date}`}</div>
17+
</>
18+
)
19+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = {}

0 commit comments

Comments
 (0)