Reload on missed Turbopack startup CSS updates
What?
Recover missed Turbopack startup CSS edits with a full page reload, while keeping ordinary CSS HMR state-preserving. Related: #99893; reuses the version-mismatch recovery introduced in #97966.
Why?
CSS can finish loading before the browser starts HMR. An edit in that window leaves old CSS in the page, but initializing the browser from the current WebSocket version hides the mismatch. The initial HTML needs to carry the version it actually rendered, and the server must observe CSS before any browser subscribes.
How?
- Capture the client HMR version before rendering and emit
__nextHmrInitialVersionin an inline script only in dev-server HTML with Turbopack HMR, using safe serialization, the existing CSP nonce and Pages crossOrigin precedence. - Use the HTML version as the first-connection fallback. Previously observed WebSocket versions retain priority on reconnect. A stale subscription reuses the existing reload message/handler.
- Await CSS-inclusive observation before serving HTML: App route client output, Pages route client output, and Pages' global
_appclient output. Pages chunks exclude dependencies already provided by_app, so the global observer is necessary for a truly fresh Pages-first load. - Reuse existing subscription ownership/deduplication and per-cycle client-version accounting. Successful observation sends no refresh/build-success message and does not invalidate the server-component cache. Subscriptions remain disabled for one-shot compilation. A genuinely missed startup JS output change can use the same recovery path.
- Remove the previous startup stylesheet reconciliation/readiness machinery and restore the original CSS loader. Remove Pages' duplicate reload action; its existing guarded hot-middleware listener already handles the message before and after hydration.
Verification
Final local runs use isolated packages:
- Turbopack: 26 passed — repeated App → Pages → App startup races, independent fresh Pages-only startup recovery, controlled delayed Pages greeting delivery, no-edit controls, global
_appJS/CSS-dependency/error/config recovery, and all app-document rendering tests. - Cache Components: 14 passed — both startup/lifecycle fixtures.
- Pages/MCP regressions: 41 passed, 10 snapshots — full/hot reload, error pages, syntax/runtime recovery and compile_route.
- Webpack: 30 passed, 10 snapshots; production/start: 12 passed. Twelve local dev-Turbopack HMR cases are appropriately gated in those modes; both unconditional HTML-absence assertions still run.
- 14 units passed, including await-before-HTML, dedicated global key, absent/dev-disabled hooks, setup rejection and cleanup-key semantics.
- Full bootstrap, root/Next/runtime types, ESLint, Prettier and whitespace checks passed. No snapshots changed.
Pre-fix isolated tests reproduced the missing crossOrigin and fresh Pages-first HTML 0 / greeting 0 failures. Tests now await actual first greeting/subscription evidence rather than treating hydration or socket-open as message readiness. They still require a newer first greeting, the original HTML subscription version, exactly one recovery navigation, current CSS, ordinary CSS state preservation and a real reconnect. Each race browser closes before file restoration so it cannot mask later startup detection.
Coverage limits: controlled delayed delivery is proven for Pages, not App. Live custom/default _app endpoint-identity add/remove and native subscription counts were not instrumented; code/dependency changes, config replacement and cleanup-key semantics are covered. Switching between distinct global CSS imports already produces competing reloads with or without the new observer; that pre-existing behavior is a separate follow-up, not ordinary same-file CSS HMR. Local browser runs used Chromium. Native/runtime source is unchanged in this follow-up; four earlier local dev-profile Rust snapshots are historical evidence, not a new release-with-assertions run. No agent CI reruns were performed, and local verification does not claim remote CI success.