Skip to content

Cloudflare workerd SSR: React module duplication in HeadContent useContext when used with TanStack Start + @cloudflare/vite-plugin #1671

@Konan69

Description

@Konan69

Setup

  • vite-plus (alias vite@voidzero-dev/vite-plus-core) 0.1.22
  • @cloudflare/vite-plugin ^1.37.2 (workerd SSR environment)
  • @tanstack/react-start ^1.168.10 + @tanstack/react-router ^1.170.7
  • React 19, @vitejs/plugin-react ^6.0.2
  • bun workspaces, single app uses TanStack Start

vite.config.ts mirrors the working garden setup exactly (vanilla Vite 8 + same environments.ssr.optimizeDeps.include list + same plugin order):

```ts
export default defineConfig({
resolve: { tsconfigPaths: true, dedupe: ["react", "react-dom"] },
environments: {
ssr: {
optimizeDeps: {
include: [
"react", "react/jsx-runtime", "react/jsx-dev-runtime",
"react-dom", "react-dom/server",
"@tanstack/react-router > @tanstack/react-store",
],
},
},
},
plugins: [
cloudflare({ viteEnvironment: { name: "ssr" } }),
agents(),
tailwindcss(),
tanstackStart(),
viteReact(),
],
});
```

What happens

SSR shell render of `HeadContent` crashes with React invalid-hook-call:

```
TypeError: Cannot read properties of null (reading 'useContext')
at exports.useContext (deps_ssr/react.js?v=…:706)
at useRouter (@tanstack/react-router/src/useRouter.tsx:20)
at useTags (@tanstack/react-router/src/headContentUtils.tsx:213)
at HeadContent (@tanstack/react-router/src/HeadContent.dev.tsx:22)
```

i.e. the classic two-copies-of-React-in-workerd-SSR situation that cloudflare/workers-sdk#11825 describes. Browser ends up on the JSON 500 page (`{"status":500,"message":"HTTPError"}`).

The exact same source tree + the exact same config, run on vanilla Vite 8 in another project (https://github.com/voidzero-dev/garden parallel — its `apps/web` works fine), renders cleanly.

Comparison

vanilla Vite 8 (garden) vite-plus-core 0.1.22 (this repo)
Same vite.config works 500 with useContext null in HeadContent
Same plugin order works broken
Same SSR optimizeDeps.include works broken

Switching this repo's catalog alias to vanilla Vite 8 doesn't fully fix it either — first `/` render works, but on any subsequent SSR render of the route (HMR reload, client navigate, etc.) the same useContext null surfaces.

Suspicion

@voidzero-dev/vite-plus-core ships its own (rolldown-backed?) Vite that handles `environments.ssr.optimizeDeps` differently from upstream Vite's dep optimizer, so the React module the workerd runner ends up with isn't the same instance the route bundle imports. This trips every provider hook downstream (useContext in HeadContent, useEffect in QueryClientProvider, etc.).

Repro repo: I can put one up if helpful — but the diff against the existing TanStack Start cloudflare example is essentially "swap upstream Vite for the vite-plus-core alias and add @cloudflare/vite-plugin".

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions