Skip to content

Commit 9d691d0

Browse files
authored
Log cached fetches during HMR refreshes if enabled in logging config (#68287)
In #67925 we decided not to log any fetches during HMR refreshes (i.e. when editing server components). With this PR, we introduce a new logging config (`logging.fetches.hmrRefreshes`) so that users can opt in to logging of `fetch` requests that are restored from the Server Components HMR cache. <img width="806" alt="hmr cache logs" src="https://github.com/user-attachments/assets/ea789520-8490-479a-a4f0-1febb42388e2">
1 parent c039891 commit 9d691d0

File tree

7 files changed

+84
-12
lines changed

7 files changed

+84
-12
lines changed

docs/02-app/02-api-reference/05-next-config-js/logging.mdx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,19 @@ module.exports = {
1919
}
2020
```
2121

22+
{/* TODO: Cross-reference severComponentsHmrCache page when #67839 has landed. */}
23+
Any `fetch` requests that are restored from the Server Components HMR cache are not logged by default. However, this can be enabled by setting `logging.fetches.hmrRefreshes` to `true`.
24+
25+
```js filename="next.config.js"
26+
module.exports = {
27+
logging: {
28+
fetches: {
29+
hmrRefreshes: true,
30+
},
31+
},
32+
}
33+
```
34+
2235
In addition, you can disable the development logging by setting `logging` to `false`.
2336

2437
```js filename="next.config.js"

packages/next/src/server/config-schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ export const configSchema: zod.ZodType<NextConfig> = z.lazy(() =>
539539
fetches: z
540540
.object({
541541
fullUrl: z.boolean().optional(),
542+
hmrRefreshes: z.boolean().optional(),
542543
})
543544
.optional(),
544545
}),

packages/next/src/server/config-shared.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,11 @@ export interface ReactCompilerOptions {
202202
export interface LoggingConfig {
203203
fetches?: {
204204
fullUrl?: boolean
205+
/**
206+
* If true, fetch requests that are restored from the HMR cache are logged
207+
* during an HMR refresh request, i.e. when editing a server component.
208+
*/
209+
hmrRefreshes?: boolean
205210
}
206211
}
207212

packages/next/src/server/dev/log-requests.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,9 @@ function logFetchMetric(
8080
url,
8181
} = fetchMetric
8282

83-
if (cacheStatus === 'hmr') {
84-
// Cache hits during HMR refreshes are intentionally not logged.
83+
if (cacheStatus === 'hmr' && !loggingConfig?.fetches?.hmrRefreshes) {
84+
// Cache hits during HMR refreshes are intentionally not logged, unless
85+
// explicitly enabled in the logging config.
8586
return
8687
}
8788

@@ -138,8 +139,13 @@ function truncateUrl(url: string): string {
138139
)
139140
}
140141

141-
function formatCacheStatus(cacheStatus: 'hit' | 'miss' | 'skip'): string {
142-
const color = cacheStatus === 'hit' ? green : yellow
143-
144-
return color(`(cache ${cacheStatus})`)
142+
function formatCacheStatus(cacheStatus: FetchMetric['cacheStatus']): string {
143+
switch (cacheStatus) {
144+
case 'hmr':
145+
return green('(HMR cache)')
146+
case 'hit':
147+
return green('(cache hit)')
148+
default:
149+
return yellow(`(cache ${cacheStatus})`)
150+
}
145151
}

test/e2e/app-dir/logging/app/fetch-no-store/page.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ export default async function Page() {
88
}
99
)
1010

11-
return <div>Hello World!</div>
11+
return <h1>Hello World!</h1>
1212
}

test/e2e/app-dir/logging/fetch-logging.test.ts

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,23 +227,69 @@ describe('app-dir - logging', () => {
227227
})
228228

229229
it('should not log requests for HMR refreshes', async () => {
230-
const browser = await next.browser('/default-cache')
230+
const browser = await next.browser('/fetch-no-store')
231231
let headline = await browser.waitForElementByCss('h1').text()
232-
expect(headline).toBe('Default Cache')
232+
expect(headline).toBe('Hello World!')
233233
const outputIndex = next.cliOutput.length
234234

235235
await next.patchFile(
236-
'app/default-cache/page.js',
237-
(content) => content.replace('Default Cache', 'Hello!'),
236+
'app/fetch-no-store/page.js',
237+
(content) => content.replace('Hello World!', 'Hello Test!'),
238238
async () =>
239239
retry(async () => {
240240
headline = await browser.waitForElementByCss('h1').text()
241-
expect(headline).toBe('Hello!')
241+
expect(headline).toBe('Hello Test!')
242242
const logs = stripAnsi(next.cliOutput.slice(outputIndex))
243+
expect(logs).toInclude(' GET /fetch-no-store')
243244
expect(logs).not.toInclude(` │ GET `)
244245
})
245246
)
246247
})
248+
249+
describe('when logging.fetches.hmrRefreshes is true', () => {
250+
beforeAll(async () => {
251+
await next.patchFile('next.config.js', (content) =>
252+
content.replace('// hmrRefreshes: true', 'hmrRefreshes: true')
253+
)
254+
})
255+
256+
afterAll(async () => {
257+
await next.patchFile('next.config.js', (content) =>
258+
content.replace('hmrRefreshes: true', '// hmrRefreshes: true')
259+
)
260+
})
261+
262+
it('should log requests for HMR refreshes', async () => {
263+
const browser = await next.browser('/fetch-no-store')
264+
let headline = await browser.waitForElementByCss('h1').text()
265+
expect(headline).toBe('Hello World!')
266+
const outputIndex = next.cliOutput.length
267+
268+
await next.patchFile(
269+
'app/fetch-no-store/page.js',
270+
(content) => content.replace('Hello World!', 'Hello Test!'),
271+
async () => {
272+
const expectedUrl = withFullUrlFetches
273+
? 'https://next-data-api-endpoint.vercel.app/api/random'
274+
: 'https://next-data-api-en../api/random'
275+
276+
return retry(async () => {
277+
headline = await browser.waitForElementByCss('h1').text()
278+
expect(headline).toBe('Hello Test!')
279+
280+
const logs = stripAnsi(
281+
next.cliOutput.slice(outputIndex)
282+
).replace(/\d+ms/g, '1ms')
283+
284+
expect(logs).toInclude(' GET /fetch-no-store')
285+
expect(logs).toInclude(
286+
` │ GET ${expectedUrl}?request-input 200 in 1ms (HMR cache)`
287+
)
288+
})
289+
}
290+
)
291+
})
292+
})
247293
}
248294
} else {
249295
// No fetches logging enabled

test/e2e/app-dir/logging/next.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module.exports = {
22
logging: {
33
fetches: {
44
fullUrl: true,
5+
// hmrRefreshes: true,
56
},
67
},
78
}

0 commit comments

Comments
 (0)