fix(rsc): keep client HMR for client modules co-located with rsc-graph code#1248
Conversation
…h code The client hotUpdate branch returns [] for any file present in the rsc module graph that is not inside a "use client" boundary, to avoid full reloads from server-only files pulled into the client graph as style deps (tailwind / addWatchFile). This also suppresses HMR for genuine client modules that happen to co-reside with rsc-graph code. A framework route file (e.g. TanStack Start) that co-locates a createServerFn with a client-rendered route component is in the rsc graph, yet the route component is a real client module (imported by the client route tree), so its Fast Refresh is wrongly suppressed and edits never reach the browser. Only suppress when the file has no non-CSS client importers, mirroring the existing rsc-branch check (importers.every(isCSSRequest)).
hi-ogawa
left a comment
There was a problem hiding this comment.
Can you add regression test in e2e?
|
Added in Triggering this outside a framework needed a specific setup. The guard only returns
Editing the component then asserts Fast Refresh with state preserved. Fails on main (the edit is dropped on the client), passes with the fix. |
|
@hi-ogawa 👋 Hi, could you please help me with how to proceed here? Thanks! |
Co-authored-by: OpenCode <noreply@opencode.ai>
Co-authored-by: OpenCode <noreply@opencode.ai>
Co-authored-by: OpenCode <noreply@opencode.ai>
Co-authored-by: OpenCode <noreply@opencode.ai>
Co-authored-by: OpenCode <noreply@opencode.ai>
Btw, the referenced PR is for the main react plugin. Rsc plugin does still support older Vite and run Vite 7 in CI still: |
Description
Problem
In dev, editing a route/page component does not Fast Refresh when that component's file also contains server‑graph code (e.g. a co‑located server function). On save, only an
(rsc)HMR update fires; the client environment receives nothing, so the DOM never updates and there is no full reload either.Root cause
In the client environment,
hotUpdatereturns[]for any file that is present in therscmodule graph and not inside a"use client"boundary:Per the comment, this guard exists to avoid full reloads when server‑only files leak into the client graph as style/watch deps (Tailwind /
addWatchFile). But the discriminator — "in the rsc graph and not a"use client"boundary" — also matches a genuine client module that merely co‑resides with rsc‑graph code.Concretely: a framework route file (e.g. TanStack Start) that co‑locates a
createServerFnwith a client‑rendered route component ends up in the rsc graph (via the server‑fn split), while the route component is a real client module imported by the client route tree — but it is not a"use client"reference, soisInsideClientBoundaryisfalse. The guard then suppresses the route component's client HMR, and editing its JSX does nothing in dev.Fix
Only
return []when the file has no non‑CSS client importers — i.e. it really is present only as a style/watch dep. This mirrors the discriminator thersc‑branch already uses (importers.every(isCSSRequest)). Files with genuine non‑CSS client importers (the route component, imported by the route tree) keep their client HMR / Fast Refresh.Reproduction
Minimal standalone repro (clone →
pnpm install && pnpm dev→ edit the route component → no update):https://github.com/sreetamdas/tanstack-rsc-hmr-co-located-serverfn-repro
It's a TanStack Start RSC app with one route whose file contains both a
createServerFnand the route component. Moving the server fn into a separate file restores HMR. Also reproduced in TanStack's owne2e/react-start/rsc(TanStack/router#7621 — a canary that fails today and passes with this fix), and confirmed on a production app (plugin-rsc@0.5.27).Open questions (this is a draft — starting the conversation before polishing)
"use client"boundary — a pattern not present in this repo's examples (thereact-routerexample uses"use client"for client routes and splits client/server across files). Happy to add an upstream test wherever you'd prefer (a dedicated example, or extending an existing one) — guidance on the shape you'd accept would help.Thanks!