Skip to content

Commit ee50d44

Browse files
ijjkKikobeats
authored andcommitted
Fix app sc_client componet HMR server-side (vercel#41510)
This ensures we properly clear the `sc_client` component cache from `react-server-dom-webpack` in development so that HMR works properly after a refresh. ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` Fixes: [slack thread](https://vercel.slack.com/archives/C043ANYDB24/p1666051202574509)
1 parent 20999e9 commit ee50d44

File tree

3 files changed

+70
-6
lines changed

3 files changed

+70
-6
lines changed

packages/next/build/webpack/plugins/nextjs-require-cache-hot-reloader.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,33 @@ export class NextJsRequireCacheHotReloader implements WebpackPluginInstance {
6868
)
6969
deleteCache(runtimeChunkPath)
7070
})
71+
let hasAppPath = false
7172

7273
// we need to make sure to clear all server entries from cache
7374
// since they can have a stale webpack-runtime cache
7475
// which needs to always be in-sync
75-
const entries = [...compilation.entries.keys()].filter(
76-
(entry) =>
77-
entry.toString().startsWith('pages/') ||
78-
entry.toString().startsWith('app/')
79-
)
76+
const entries = [...compilation.entries.keys()].filter((entry) => {
77+
const isAppPath = entry.toString().startsWith('app/')
78+
hasAppPath = hasAppPath || isAppPath
79+
return entry.toString().startsWith('pages/') || isAppPath
80+
})
81+
82+
if (hasAppPath) {
83+
// ensure we reset the cache for sc_server components
84+
// loaded via react-server-dom-webpack
85+
const reactWebpackModId = require.resolve(
86+
'next/dist/compiled/react-server-dom-webpack'
87+
)
88+
const reactWebpackMod = require.cache[reactWebpackModId]
89+
90+
if (reactWebpackMod) {
91+
for (const child of reactWebpackMod.children) {
92+
child.parent = null
93+
delete require.cache[child.id]
94+
}
95+
}
96+
delete require.cache[reactWebpackModId]
97+
}
8098

8199
entries.forEach((page) => {
82100
const outputPath = path.join(

packages/next/server/app-render.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import type { FontLoaderManifest } from '../build/webpack/plugins/font-loader-ma
88
import React, { experimental_use as use } from 'react'
99

1010
import { ParsedUrlQuery } from 'querystring'
11-
import { createFromReadableStream } from 'next/dist/compiled/react-server-dom-webpack'
1211
import { NextParsedUrlQuery } from './request-meta'
1312
import RenderResult from './render-result'
1413
import {
@@ -271,6 +270,9 @@ function useFlightResponse(
271270
if (flightResponseRef.current !== null) {
272271
return flightResponseRef.current
273272
}
273+
const {
274+
createFromReadableStream,
275+
} = require('next/dist/compiled/react-server-dom-webpack')
274276

275277
const [renderStream, forwardStream] = readableStreamTee(req)
276278
const res = createFromReadableStream(renderStream, {

test/e2e/app-dir/index.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,50 @@ describe('app dir', () => {
11971197
})
11981198

11991199
if (isDev) {
1200+
it('should HMR correctly for client component', async () => {
1201+
const filePath = 'app/client-component-route/page.js'
1202+
const origContent = await next.readFile(filePath)
1203+
1204+
try {
1205+
const browser = await webdriver(next.url, '/client-component-route')
1206+
1207+
const ssrInitial = await renderViaHTTP(
1208+
next.url,
1209+
'/client-component-route'
1210+
)
1211+
1212+
expect(ssrInitial).toContain(
1213+
'hello from app/client-component-route'
1214+
)
1215+
1216+
expect(await browser.elementByCss('p').text()).toContain(
1217+
'hello from app/client-component-route'
1218+
)
1219+
1220+
await next.patchFile(
1221+
filePath,
1222+
origContent.replace('hello from', 'swapped from')
1223+
)
1224+
1225+
await check(() => browser.elementByCss('p').text(), /swapped from/)
1226+
1227+
const ssrUpdated = await renderViaHTTP(
1228+
next.url,
1229+
'/client-component-route'
1230+
)
1231+
expect(ssrUpdated).toContain('swapped from')
1232+
1233+
await next.patchFile(filePath, origContent)
1234+
1235+
await check(() => browser.elementByCss('p').text(), /hello from/)
1236+
expect(
1237+
await renderViaHTTP(next.url, '/client-component-route')
1238+
).toContain('hello from')
1239+
} finally {
1240+
await next.patchFile(filePath, origContent)
1241+
}
1242+
})
1243+
12001244
it('should throw an error when getServerSideProps is used', async () => {
12011245
const pageFile =
12021246
'app/client-with-errors/get-server-side-props/page.js'

0 commit comments

Comments
 (0)