cloudflare / cloudflare/workers-sdk

SSR route hangs forever in dev when the route's first render is a notFound() (TanStack Start + @cloudflare/vite-plugin)

Open
#14,917 3 comments 0 reactions 0 assignees View on GitHub
package: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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.