Skip to content

next.config.js: Accept an option for serverFastRefresh#91968

Merged
wbinnssmith merged 9 commits intocanaryfrom
wbinnssmith/server-hmr-config
Mar 31, 2026
Merged

next.config.js: Accept an option for serverFastRefresh#91968
wbinnssmith merged 9 commits intocanaryfrom
wbinnssmith/server-hmr-config

Conversation

@wbinnssmith
Copy link
Copy Markdown
Member

@wbinnssmith wbinnssmith commented Mar 27, 2026

What?

Adds experimental.serverFastRefresh to next.config.js so users (and Next.js plugins) can opt out of server-side Fast Refresh without needing to pass a CLI flag.

Also adds a --server-fast-refresh positive CLI flag (hidden) alongside the existing --no-server-fast-refresh, and emits a warning when the CLI flag and config value conflict. This is a necessary implementation detail for commander to detect when no option is provided.

Why?

Certain Next.js plugins have encountered issues with server Fast Refresh and need a way to disable it programmatically via config rather than requiring users to modify their start scripts. The CLI-only opt-out (--no-server-fast-refresh) was insufficient for this use case.

How?

  • config-shared.ts / config-schema.ts: Added experimental.serverFastRefresh?: boolean to the ExperimentalConfig type and Zod schema.
  • router-server.ts: Resolves the effective serverFastRefresh value at startup by combining the CLI flag (opts.serverFastRefresh) and nextConfig.experimental.turbopackServerFastRefresh, with CLI taking precedence. Emits a Log.warn when both are set to conflicting values. Defaults to true when neither is specified.
  • bin/next.ts: Fixed the Commander option definition for --no-server-fast-refresh. Commander's --no-X pattern implicitly creates a positive --server-fast-refresh option defaulting to true, which meant opts.serverFastRefresh was never undefined and the config value could never take effect. Fixed by explicitly registering a hidden --server-fast-refresh option with .default(undefined).
  • Tests: Added two new test suites in test/development/app-dir/server-hmr/server-hmr.test.ts:
    • server-hmr config opt-out — verifies that setting experimental.serverFastRefresh: false in config causes unmodified dependencies to be re-evaluated (i.e., server HMR is disabled).
    • server-hmr CLI/config conflict warning — verifies that passing --no-server-fast-refresh when config has serverFastRefresh: true logs the conflict warning.

Test Plan

  • Added e2e development tests (test/development/app-dir/server-hmr/server-hmr.test.ts) — all 8 tests pass

Currently we support opting out of server fast refresh via the cli.  Certain Next.js plugins have encountered issues with it (which we're addressing separately) and may want to opt the user out for the time being. This implements that.

Test Plan:

- [ ] Added e2e development test
@nextjs-bot
Copy link
Copy Markdown
Collaborator

nextjs-bot commented Mar 27, 2026

Tests Passed

Comment thread packages/next/src/bin/next.ts Outdated
…sing test fixtures

- Fix Commander's --no-server-fast-refresh option to correctly produce
  `undefined` (not `true`) when no flag is passed, by adding a hidden
  positive `--server-fast-refresh` option with `default(undefined)`
- Add `?? true` default in router-server.ts so server HMR is enabled
  when neither CLI nor config specifies a value
- Remove non-existent tsconfig.json FileRef from test suites (tsconfig
  is auto-generated by the test harness; test/.gitignore ignores it)

Co-Authored-By: Claude <[email protected]>
@nextjs-bot
Copy link
Copy Markdown
Collaborator

nextjs-bot commented Mar 27, 2026

Stats from current PR

✅ No significant changes detected

📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 456ms 455ms ▁▁▁▁█
Cold (Ready in log) 439ms 439ms ▁▁▁▁█
Cold (First Request) 1.115s 1.096s ▂▁▂▂▆
Warm (Listen) 457ms 457ms ▁▁▁▁█
Warm (Ready in log) 443ms 443ms ▁▁▁▁█
Warm (First Request) 338ms 342ms ▁▁▁▁▇
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 456ms 454ms █▁▁▁▁
Cold (Ready in log) 437ms 436ms █▁▃▁▁
Cold (First Request) 1.907s 1.926s █▃▅▃▄
Warm (Listen) 456ms 456ms █▁▁▁▁
Warm (Ready in log) 436ms 436ms █▁▃▁▁
Warm (First Request) 1.940s 1.925s █▄▅▄▃

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 3.826s 3.774s ▁▁▁▁▇
Cached Build 3.848s 3.821s ▁▁▁▁▇
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 14.191s 14.275s █▁▃▁▁
Cached Build 14.355s 14.395s █▁▂▁▁
node_modules Size 485 MB 485 MB ▁████
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
02fkg8wfh0iju.js gzip 9.19 kB N/A -
050zwt5xh_0tx.js gzip 10.4 kB N/A -
06rvbj82bhyo0.js gzip 13 kB N/A -
087fzjd-gvlzv.js gzip 450 B N/A -
090424vt6v7ds.js gzip 159 B N/A -
0aol1ui30rfbo.js gzip 157 B N/A -
0cz1d0mv5g_q7.js gzip 39.4 kB 39.4 kB
0gelg_5b_e8qe.js gzip 155 B N/A -
0ppxcl_z43mad.js gzip 8.52 kB N/A -
14li7483qi75h.js gzip 163 B N/A -
16bgv0rl50_pu.js gzip 159 B N/A -
16ijf4c25ys80.js gzip 65.7 kB N/A -
19e0yjtsaya4i.js gzip 155 B N/A -
19oha6-znmkcv.js gzip 8.55 kB N/A -
1elt1qium-r2m.css gzip 115 B 115 B
1wjj5n9v-ra3c.js gzip 161 B N/A -
1yl5jy653a49k.js gzip 13.7 kB N/A -
219prxwxgaalc.js gzip 7.61 kB N/A -
26elcgxnn9zjd.js gzip 8.52 kB N/A -
26mi1ohw-mb8a.js gzip 157 B N/A -
2900hudr6gvm0.js gzip 2.28 kB N/A -
2ihlsvbo2c9e8.js gzip 215 B 215 B
2lv2js3kmdeho.js gzip 8.48 kB N/A -
2nka2u3cdrbc3.js gzip 157 B N/A -
2rehygrd36hqv.js gzip 8.58 kB N/A -
2srwswih0m9_h.js gzip 13.3 kB N/A -
3-p9p9mheqhzx.js gzip 8.55 kB N/A -
31030bryqpolg.js gzip 8.53 kB N/A -
31dx5nmrzzuy7.js gzip 225 B N/A -
3ha7h58bte-tq.js gzip 49 kB N/A -
3hvwvoj6f6t3n.js gzip 157 B N/A -
3iu80eefg23ae.js gzip 9.77 kB N/A -
3k-48b78ys_vy.js gzip 10.1 kB N/A -
3lal86c5wf_yg.js gzip 70.8 kB N/A -
3m7-5rfj0avoz.js gzip 12.9 kB N/A -
3uqce_6sa526g.js gzip 8.47 kB N/A -
3utn9zffa8u9_.js gzip 158 B N/A -
3yurjqk-sjs3y.js gzip 1.46 kB N/A -
3zqtdtmi3th1f.js gzip 168 B N/A -
421vzwdt9j1b_.js gzip 5.62 kB N/A -
43fgvwkt49o4d.js gzip 155 B N/A -
turbopack-08..talh.js gzip 4.16 kB N/A -
turbopack-0h..652d.js gzip 4.16 kB N/A -
turbopack-0j..2q0l.js gzip 4.16 kB N/A -
turbopack-1_..qh8b.js gzip 4.16 kB N/A -
turbopack-18..pspt.js gzip 4.16 kB N/A -
turbopack-1c..t_nc.js gzip 4.16 kB N/A -
turbopack-1e..87qg.js gzip 4.16 kB N/A -
turbopack-20..29ar.js gzip 4.14 kB N/A -
turbopack-28..sdcw.js gzip 4.16 kB N/A -
turbopack-2c..qqky.js gzip 4.16 kB N/A -
turbopack-32..ytfp.js gzip 4.17 kB N/A -
turbopack-3d..3upq.js gzip 4.16 kB N/A -
turbopack-3m..uq4z.js gzip 4.16 kB N/A -
turbopack-44..ylbr.js gzip 4.16 kB N/A -
03dgzoo-qf3sm.js gzip N/A 9.19 kB -
05tx5f25dlivn.js gzip N/A 8.53 kB -
09m2s5u40m5og.js gzip N/A 157 B -
09mxvv6aiydb_.js gzip N/A 65.7 kB -
0byha61s8vekp.js gzip N/A 153 B -
0c7ez6p2qc57f.js gzip N/A 5.62 kB -
0d5nirtpjn062.js gzip N/A 158 B -
0jj4c4erjtxsg.js gzip N/A 155 B -
0m-34rm9w_wpm.js gzip N/A 7.6 kB -
0n95_eeqgrhcd.js gzip N/A 13.7 kB -
0qnwuk92m8i7o.js gzip N/A 10.4 kB -
0r4wrn6n0ue2m.js gzip N/A 8.55 kB -
0rp0fodtbt_6m.js gzip N/A 8.52 kB -
0s8ggcw6mh4_o.js gzip N/A 155 B -
0sfck-km4dl1k.js gzip N/A 8.47 kB -
0x0xuhmxzwkp8.js gzip N/A 8.47 kB -
1_lv_p0r18aao.js gzip N/A 156 B -
1-wdvgxnzicj7.js gzip N/A 1.46 kB -
1-xvi535rn1l6.js gzip N/A 162 B -
11u6nxujb2eg4.js gzip N/A 450 B -
13xk693haunp3.js gzip N/A 157 B -
1c-l-bq3c2e8h.js gzip N/A 159 B -
1pta-8auqoi6l.js gzip N/A 155 B -
1wp95nq70u1i5.js gzip N/A 156 B -
2hiott03n-mbw.js gzip N/A 70.8 kB -
2k9ax08cjl2id.js gzip N/A 12.9 kB -
2lms6k76q5-6m.js gzip N/A 13.3 kB -
2q3hz-jtbjs52.js gzip N/A 9.77 kB -
2qx4twi9i3xus.js gzip N/A 2.28 kB -
2srnqic6tvxxd.js gzip N/A 8.52 kB -
30l7m4nayp73a.js gzip N/A 8.55 kB -
38rr7d3kfutni.js gzip N/A 13 kB -
3axctu0hicx1a.js gzip N/A 49 kB -
3h_ecpiaatwgc.js gzip N/A 10.1 kB -
3ity0aahajapd.js gzip N/A 225 B -
3on1m8x6fltmq.js gzip N/A 158 B -
3th0yfyrh2ztp.js gzip N/A 170 B -
43mlw9dy_8f02.js gzip N/A 8.58 kB -
turbopack-01..3su2.js gzip N/A 4.16 kB -
turbopack-02..s4_4.js gzip N/A 4.16 kB -
turbopack-11..49ru.js gzip N/A 4.18 kB -
turbopack-16..akma.js gzip N/A 4.16 kB -
turbopack-1g..kove.js gzip N/A 4.16 kB -
turbopack-1i..lysy.js gzip N/A 4.16 kB -
turbopack-21..dbux.js gzip N/A 4.16 kB -
turbopack-2q..j2xt.js gzip N/A 4.16 kB -
turbopack-2t..m6lo.js gzip N/A 4.14 kB -
turbopack-31..szs4.js gzip N/A 4.16 kB -
turbopack-35..-iv0.js gzip N/A 4.16 kB -
turbopack-37..max7.js gzip N/A 4.16 kB -
turbopack-38..03ad.js gzip N/A 4.16 kB -
turbopack-3x..jc9x.js gzip N/A 4.16 kB -
Total 464 kB 464 kB ✅ -8 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 719 B 715 B
Total 719 B 715 B ✅ -4 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 435 B 434 B
Total 435 B 434 B ✅ -1 B

📦 Webpack

Client

Main Bundles
Canary PR Change
5528-HASH.js gzip 5.54 kB N/A -
6280-HASH.js gzip 60.7 kB N/A -
6335.HASH.js gzip 169 B N/A -
912-HASH.js gzip 4.59 kB N/A -
e8aec2e4-HASH.js gzip 62.8 kB N/A -
framework-HASH.js gzip 59.7 kB 59.7 kB
main-app-HASH.js gzip 255 B 253 B
main-HASH.js gzip 39.3 kB 39.2 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
262-HASH.js gzip N/A 4.59 kB -
2889.HASH.js gzip N/A 169 B -
5602-HASH.js gzip N/A 5.55 kB -
6948ada0-HASH.js gzip N/A 62.8 kB -
9544-HASH.js gzip N/A 61.4 kB -
Total 235 kB 235 kB ⚠️ +665 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Total 39.4 kB 39.4 kB
Pages
Canary PR Change
_app-HASH.js gzip 194 B 194 B
_error-HASH.js gzip 183 B 180 B 🟢 3 B (-2%)
css-HASH.js gzip 331 B 330 B
dynamic-HASH.js gzip 1.81 kB 1.81 kB
edge-ssr-HASH.js gzip 256 B 256 B
head-HASH.js gzip 351 B 352 B
hooks-HASH.js gzip 384 B 383 B
image-HASH.js gzip 580 B 581 B
index-HASH.js gzip 260 B 260 B
link-HASH.js gzip 2.51 kB 2.51 kB
routerDirect..HASH.js gzip 320 B 319 B
script-HASH.js gzip 386 B 386 B
withRouter-HASH.js gzip 315 B 315 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.98 kB 7.98 kB ✅ -1 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 125 kB 125 kB
page.js gzip 270 kB 270 kB
Total 395 kB 396 kB ⚠️ +228 B
Middleware
Canary PR Change
middleware-b..fest.js gzip 618 B 617 B
middleware-r..fest.js gzip 156 B 155 B
middleware.js gzip 44.1 kB 44 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 45.7 kB 45.7 kB ✅ -32 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 715 B 718 B
Total 715 B 718 B ⚠️ +3 B
Build Cache
Canary PR Change
0.pack gzip 4.35 MB 4.34 MB 🟢 8.69 kB (0%)
index.pack gzip 111 kB 109 kB 🟢 1.3 kB (-1%)
index.pack.old gzip 110 kB 110 kB
Total 4.57 MB 4.56 MB ✅ -9.7 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 335 kB 335 kB
app-page-exp..prod.js gzip 185 kB 185 kB
app-page-tur...dev.js gzip 335 kB 335 kB
app-page-tur..prod.js gzip 185 kB 185 kB
app-page-tur...dev.js gzip 332 kB 332 kB
app-page-tur..prod.js gzip 183 kB 183 kB
app-page.run...dev.js gzip 332 kB 332 kB
app-page.run..prod.js gzip 184 kB 184 kB
app-route-ex...dev.js gzip 76.2 kB 76.2 kB
app-route-ex..prod.js gzip 51.9 kB 51.9 kB
app-route-tu...dev.js gzip 76.3 kB 76.3 kB
app-route-tu..prod.js gzip 51.9 kB 51.9 kB
app-route-tu...dev.js gzip 75.9 kB 75.9 kB
app-route-tu..prod.js gzip 51.6 kB 51.6 kB
app-route.ru...dev.js gzip 75.8 kB 75.8 kB
app-route.ru..prod.js gzip 51.6 kB 51.6 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 43.5 kB 43.5 kB
pages-api-tu..prod.js gzip 33.1 kB 33.1 kB
pages-api.ru...dev.js gzip 43.4 kB 43.4 kB
pages-api.ru..prod.js gzip 33.1 kB 33.1 kB
pages-turbo....dev.js gzip 52.9 kB 52.9 kB
pages-turbo...prod.js gzip 38.7 kB 38.7 kB
pages.runtim...dev.js gzip 52.8 kB 52.8 kB
pages.runtim..prod.js gzip 38.7 kB 38.7 kB
server.runti..prod.js gzip 62.5 kB 62.5 kB
Total 2.98 MB 2.98 MB ✅ -10 B
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/dea454122adb5d21fe3049d6cd7495b5869ab92c/next

@wbinnssmith wbinnssmith marked this pull request as ready for review March 27, 2026 04:15
Comment thread test/development/app-dir/server-hmr/server-hmr.test.ts
Comment thread packages/next/src/server/lib/router-server.ts Outdated
Comment thread packages/next/src/server/config-schema.ts Outdated
wbinnssmith and others added 2 commits March 27, 2026 17:07
Addresses reviewer feedback: since this feature is Turbopack-specific,
it belongs under experimental with the turbopack* naming convention.

Co-Authored-By: Claude <[email protected]>
@wbinnssmith wbinnssmith enabled auto-merge (squash) March 28, 2026 17:35
@wbinnssmith wbinnssmith merged commit a21f3f4 into canary Mar 31, 2026
398 of 410 checks passed
@wbinnssmith wbinnssmith deleted the wbinnssmith/server-hmr-config branch March 31, 2026 00:20
wbinnssmith added a commit that referenced this pull request Mar 31, 2026
### What?

Adds `experimental.serverFastRefresh` to `next.config.js` so users (and
Next.js plugins) can opt out of server-side Fast Refresh without needing
to pass a CLI flag.

Also adds a `--server-fast-refresh` positive CLI flag (hidden) alongside
the existing `--no-server-fast-refresh`, and emits a warning when the
CLI flag and config value conflict. This is a necessary implementation
detail for `commander` to detect when no option is provided.

### Why?

Certain Next.js plugins have encountered issues with server Fast Refresh
and need a way to disable it programmatically via config rather than
requiring users to modify their start scripts. The CLI-only opt-out
(`--no-server-fast-refresh`) was insufficient for this use case.

### How?

- **`config-shared.ts` / `config-schema.ts`**: Added
`experimental.serverFastRefresh?: boolean` to the `ExperimentalConfig`
type and Zod schema.
- **`router-server.ts`**: Resolves the effective `serverFastRefresh`
value at startup by combining the CLI flag (`opts.serverFastRefresh`)
and `nextConfig.experimental.serverFastRefresh`, with CLI taking
precedence. Emits a `Log.warn` when both are set to conflicting values.
Defaults to `true` when neither is specified.
- **`bin/next.ts`**: Fixed the Commander option definition for
`--no-server-fast-refresh`. Commander's `--no-X` pattern implicitly
creates a positive `--server-fast-refresh` option defaulting to `true`,
which meant `opts.serverFastRefresh` was never `undefined` and the
config value could never take effect. Fixed by explicitly registering a
hidden `--server-fast-refresh` option with `.default(undefined)`.
- **Tests**: Added two new test suites in
`test/development/app-dir/server-hmr/server-hmr.test.ts`:
- `server-hmr config opt-out` — verifies that setting
`experimental.serverFastRefresh: false` in config causes unmodified
dependencies to be re-evaluated (i.e., server HMR is disabled).
- `server-hmr CLI/config conflict warning` — verifies that passing
`--no-server-fast-refresh` when config has `serverFastRefresh: true`
logs the conflict warning.

### Test Plan

- [x] Added e2e development tests
(`test/development/app-dir/server-hmr/server-hmr.test.ts`) — all 8 tests
pass

---------

Co-authored-by: Will Binns-Smith <[email protected]>
Co-authored-by: Claude <[email protected]>
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants