Skip to content

Instantly share code, notes, and snippets.

@joshmatz
Created August 5, 2026 15:30
Show Gist options
  • Select an option

  • Save joshmatz/e26ba38aaf5b087809c01de5d25e05ec to your computer and use it in GitHub Desktop.

Select an option

Save joshmatz/e26ba38aaf5b087809c01de5d25e05ec to your computer and use it in GitHub Desktop.
TanStack Start + Cloudflare development HMR failures on joshmatz.com

TanStack Start + Cloudflare development HMR failures on joshmatz.com

TL;DR

I moved joshmatz.com from Hugo to TanStack Start on Cloudflare Workers in February 2026. HMR began failing almost immediately, and it later failed again in July in two distinct ways.

The specific problems we reproduced now appear to be resolved:

  1. An old Cloudflare compatibility flag, added as an early workaround, eventually made the current runtime less stable and caused the development worker to return 500s or stop responding during live HMR.
  2. Cloudflare Vite plugin 1.25.6 selected the wrong Vite module runner for Vite 7.3.1. During SSR HMR this could load two React instances, producing Invalid hook call and null useContext errors while rendering TanStack's HeadContent.

Removing the obsolete flag and upgrading the Cloudflare plugin, Vite, Wrangler, TanStack Start, and React resolved both reproduced failures. I would describe this as a problem in the TanStack Start + Cloudflare SSR development integration. The concrete upstream bug we identified was in Cloudflare's Vite plugin.

What happened

February: the initial migration immediately needed an HMR workaround

The site moved to TanStack Start in this February 28 migration. The initial stack included:

  • TanStack Start and Router 1.163
  • Vite 7.3.1
  • Cloudflare Vite plugin 1.25.6
  • Wrangler 4.69
  • React 19.2.4

Nine minutes later, Claude committed an HMR crash workaround. Its commit message describes "alternating SSR cancellation errors during HMR." It added:

  • Cloudflare's no_handle_cross_request_promise_resolution compatibility flag
  • A 200 ms awaitWriteFinish delay to Vite's watcher

This establishes that the July failure was not the first incident, even though I no longer have the February conversation transcript.

July: the development server wedged during live HMR

In July, the development worker could remain bound to port 3000 while returning 500s or no longer accepting HTTP requests.

We tested SSR invalidations both with and without a browser maintaining an HMR connection:

  • 40 invalidations without a live browser connection completed successfully.
  • With live browser HMR connected, the third invalidation failed.
  • After removing only no_handle_cross_request_promise_resolution, 80 consecutive live-browser invalidations completed successfully.

That flag had become counterproductive. Cloudflare's corrected cross-request promise behavior has been the default since compatibility date 2024-10-14; the no_handle_... flag disables it. Cloudflare documents the behavior in its compatibility flags reference.

A second July failure: duplicate React during SSR HMR

Removing the compatibility flag fixed the persistent 500s, but another HMR failure remained. A page would update automatically, show an error, and work again only after a manual refresh.

Once captured in the server logs, the failure included:

  • [vite] program reload
  • Invalid hook call
  • Cannot read properties of null (reading 'useContext')
  • A stack involving TanStack's HeadContent

Cloudflare Vite plugin 1.25.6 was selecting an incompatible vite/module-runner for Vite 7.3.1. During SSR HMR, TanStack's HeadContent could consequently render with a different React instance from react-dom/server.

Cloudflare fixed the module-runner selection problem in Workers SDK PR #12936, released in @cloudflare/vite-plugin 1.29.1. The site's lockfile still had plugin 1.25.6 in July, so it was not receiving that fix.

Resolution and validation

The final fix is in Update site dependencies. It removed the obsolete compatibility flag and upgraded the stack to:

  • Cloudflare Vite plugin 1.47.0
  • Vite 8.1.5
  • Wrangler 4.114.0
  • TanStack Start 1.168.32
  • TanStack Router 1.170.18
  • React and React DOM 19.2.8

After the upgrade, six consecutive real MDX edits caused six SSR reloads without an invalid hook call, null useContext, 500 response, or manual refresh. The production build and typecheck also passed.

Cloudflare now documents TanStack Start as supported on Workers, including SSR and HMR. The current project configuration follows the core setup in Cloudflare's TanStack Start guide and Vite plugin documentation.

Current conclusion

The failures we reproduced appear to be fixed both upstream and in this project. Six successful edit cycles are not proof that every HMR edge case is gone, but I have not found another HMR-specific repair in the repository after the July dependency update.

The agent-generated setup mattered. The original workaround was reasonable before the upstream module-runner fix existed, but it remained in the project after Cloudflare's behavior changed, while the lockfile kept the plugin version old enough to miss the upstream repair. That combination turned an old workaround and an old integration bug into two different failures with similar symptoms.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment