cloudflare / cloudflare/workers-sdk
SSR route hangs forever in dev when the route's first render is a notFound() (TanStack Start + @cloudflare/vite-plugin)
- Dominant language
- TypeScript
- Stars
- 4.5k
- Forks
- 1.5k
- Avg merge
- 3d 8h
- Merged PRs (30d)
- 187
Description
### What versions & operating system are you using?
`@cloudflare/vite-plugin` 1.43.0 **and** 1.48.0 (both affected) · wrangler 4.115.0 · vite 8.1.5 · `@tanstack/react-start` 1.168.26 · `@tanstack/react-router` 1.170.16 · react 19.2.7 · node 23.5.0 · pnpm 11.13.0 · macOS 15 (arm64)
Plugin configured as `cloudflare({ viteEnvironment: { name: 'ssr' } })` and listed first, per Cloudflare's TanStack Start framework guide.
### Please provide a link to a minimal reproduction
https://github.com/hakonrossebo/tanstack-start-cf-notfound-hang
### Describe the Bug
With `@cloudflare/vite-plugin` and TanStack Start, if the **first** request to a route in a fresh dev server results in `notFound()` thrown from the route loader, then **every subsequent request to that same route hangs forever** — 0 bytes, no headers, no error, nothing in the server log. It does not recover (measured silent for 150 s).
On the hanging request the route's **loader still runs to completion and returns its data** (verified with `console.log` probes), but **no component renders** — not even the root component. So the stall happens between "loader resolved" and "render start".
If the route's **first** render is a successful one, the route is **immune** to `notFound()` for the rest of the process's lifetime. It is not the 404 that poisons the route — it is which render happens to be the route's first.
Other routes are unaffected: while the poisoned route hangs, `/` and other SSR routes respond in milliseconds. The dev server itself stays healthy.
**Removing `@cloudflare/vite-plugin` makes it green.** That is the only change between the rows below — same app code, same versions, same everything else.
| Setup (identical app code) | notFound first | then a valid route |
|---|---|---|
| TanStack Start + Vite, **without** the plugin | 404 in 0.007 s | **200 in 0.005 s — OK** |
| **+ `@cloudflare/vite-plugin` 1.43.0** | 404 in 0.009 s | **hangs — 30 s, 0 bytes** |
| **+ `@cloudflare/vite-plugin` 1.48.0** (latest) | 404 in 0.008 s | **hangs — 40 s, 0 bytes** |
| 1.48.0, successful render first (control) | — | 200 / 404 / 200 — all fine |
Production builds are **not** affected: the equivalent sequence measured 45/45 clean against a deployed Worker. This is dev-only.
**Reproduce** (the linked repo is ~40 lines of app code — no database, no cache, no server functions, no auth; one route whose loader throws `notFound()` for id `missing` and succeeds otherwise):
```bash
pnpm install && pnpm dev
curl -s -o /dev/null -w '%{http_code} %{time_total}s\n' http://localhost:3400/item/missing # 404, fast
curl -s -o /dev/null -w '%{http_code} %{time_total}s\n' --max-time 30 http://localhost:3400/item/exists
# ^ hangs: no response, no error, no log
```
Two scripts in the repo run both sequences: `repro.sh` (with the plugin) and `repro-without-cloudflare.sh` (the control).
We hit this in a real app and worked around it for months by warming every route with one successful render before any 404 in our test gates. Reducing it to a minimal project is what finally isolated the plugin as the required ingredient.
Possibly related: #13952 (same plugin combination with `viteEnvironment: { name: "ssr" }`), #11825 (module duplication in the workerd SSR environment), and TanStack/router#6045 (empty response bodies from streaming server functions with this plugin).
### Please provide any relevant error logs
There are none — that is part of the symptom. The probe output on the hanging request is:
```
[PROBE] RootComponent render <- the notFound request renders fine
[PROBE] RootDocument render
[PROBE] loader start missing
[PROBE] RootComponent render
[PROBE] RootDocument render
[PROBE] loader start exists <- the HANGING request
[PROBE] loader ok exists <- loader completes and returns data ...
<- ... and then nothing. No render, no bytes, no error.
```
Contributor guide
Research direction
Start with the linked minimal reproduction and run repro.sh alongside repro-without-cloudflare.sh to compare the Cloudflare plugin and control behavior. Inspect the dev SSR path configured with viteEnvironment: { name: "ssr" }, focusing on the transition after the loader resolves and before rendering starts. Done means a route first rendered with notFound() responds normally to a later successful request in dev, without regressing the control or production behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, react, typescript, vite
- Domain
- backend, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100