[GH-ISSUE #50] bug: Invalid hook call + Cannot read properties of null (reading 'useContext') #13

Closed
opened 2026-05-06 12:36:29 +02:00 by BreizhHardware · 3 comments

Originally created by @XavierGeerinck on GitHub (Feb 25, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/50

Very nice project! Thanks for making this possible.

I tried migrating, but can't yet as I get the below, any idea?

Analysis

Root cause: React 19 dev-mode crashes in Vinext SSR/RSC outside Next.js

When running react-server-dom-webpack/client.edge in Vite's SSR environment (via @vitejs/plugin-rsc), two React 19 dev-mode code paths crash because ReactSharedInternals.H (the hooks dispatcher) is null outside a React render cycle:

  1. resolveErrorDev — When an RSC stream contains an error, this function builds fake call stacks using eval'd closures that ultimately call new Error(). React 19 dev mode intercepts Error construction to capture component stacks, which calls H.useContext → crashes on null.useContext.

  2. resolveDispatcher — React's own resolveDispatcher() returns null when H is unset (outside render), and callers do dispatcher.useContext(Context) → same null crash. This fires during SSR module loading and RSC stream processing.

Next.js avoids this because it always sets up the dispatcher before processing RSC streams. Standalone Vite + @vitejs/plugin-rsc does not.

Additionally: Package dist files built with tsdown/tsup often strip "use client" directives from barrel files. This causes the RSC plugin to treat client components (ThemeProvider, Toaster, auth hooks) as server components, leading to "Functions cannot be passed directly to Client Components" serialization errors.

Original Trace

Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.
1:21:35 PM [vite] Internal server error: Cannot read properties of null (reading 'useContext')
      at _resolveErrorDevImpl (/Users/xaviergeerinck/conductor/workspaces/main/cairo/apps/analytics/node_modules/.vite/deps_ssr/react-server-dom-webpack_client__edge.js:4028:148)
      at resolveErrorDev (/Users/xaviergeerinck/conductor/workspaces/main/cairo/apps/analytics/node_modules/.vite/deps_ssr/react-server-dom-webpack_client__edge.js:4003:20)
      at processFullBinaryRow (/Users/xaviergeerinck/conductor/workspaces/main/cairo/apps/analytics/node_modules/.vite/deps_ssr/react-server-dom-webpack_client__edge.js:4819:29)
      at progress (/Users/xaviergeerinck/conductor/workspaces/main/cairo/apps/analytics/node_modules/.vite/deps_ssr/react-server-dom-webpack_client__edge.js:5015:20)

Suggestion

The proper fix should happen at two levels:

1. Vinext/@vitejs/plugin-rsc should set up the React dispatcher before processing RSC streams

Next.js does this internally — before calling into react-server-dom-webpack/client.edge to process RSC streams in SSR, it ensures ReactSharedInternals.H is initialized. Vinext's SSR runner should do the same. The fix would be in the SSR entry point that calls createFromFetch/createFromReadableStream, wrapping it inside a React render context (or manually setting up a minimal dispatcher).

2. Vinext should auto-detect missing "use client" directives

Many packages (next-themes, sonner, better-auth/react) ship dist files without "use client" because Next.js infers it from the module graph. Vinext could:

  • Option A: Provide a config option like clientModules: [/next-themes/, /sonner/] that tells the RSC plugin to treat matching modules as client boundaries
  • Option B: Heuristically detect modules that import React hooks (useState, useContext, etc.) and auto-mark them as client modules in the RSC environment
  • Option C: Document a recommended injectClientDirectives plugin pattern for users to handle this themselves
Originally created by @XavierGeerinck on GitHub (Feb 25, 2026). Original GitHub issue: https://github.com/cloudflare/vinext/issues/50 Very nice project! Thanks for making this possible. I tried migrating, but can't yet as I get the below, any idea? ## Analysis **Root cause: React 19 dev-mode crashes in Vinext SSR/RSC outside Next.js** When running `react-server-dom-webpack/client.edge` in Vite's SSR environment (via `@vitejs/plugin-rsc`), two React 19 dev-mode code paths crash because `ReactSharedInternals.H` (the hooks dispatcher) is `null` outside a React render cycle: 1. **`resolveErrorDev`** — When an RSC stream contains an error, this function builds fake call stacks using `eval`'d closures that ultimately call `new Error()`. React 19 dev mode intercepts `Error` construction to capture component stacks, which calls `H.useContext` → crashes on `null.useContext`. 2. **`resolveDispatcher`** — React's own `resolveDispatcher()` returns `null` when `H` is unset (outside render), and callers do `dispatcher.useContext(Context)` → same null crash. This fires during SSR module loading and RSC stream processing. Next.js avoids this because it always sets up the dispatcher before processing RSC streams. Standalone Vite + `@vitejs/plugin-rsc` does not. **Additionally:** Package dist files built with `tsdown`/`tsup` often strip `"use client"` directives from barrel files. This causes the RSC plugin to treat client components (ThemeProvider, Toaster, auth hooks) as server components, leading to "Functions cannot be passed directly to Client Components" serialization errors. ## Original Trace ``` Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3. You might have more than one copy of React in the same app See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem. 1:21:35 PM [vite] Internal server error: Cannot read properties of null (reading 'useContext') at _resolveErrorDevImpl (/Users/xaviergeerinck/conductor/workspaces/main/cairo/apps/analytics/node_modules/.vite/deps_ssr/react-server-dom-webpack_client__edge.js:4028:148) at resolveErrorDev (/Users/xaviergeerinck/conductor/workspaces/main/cairo/apps/analytics/node_modules/.vite/deps_ssr/react-server-dom-webpack_client__edge.js:4003:20) at processFullBinaryRow (/Users/xaviergeerinck/conductor/workspaces/main/cairo/apps/analytics/node_modules/.vite/deps_ssr/react-server-dom-webpack_client__edge.js:4819:29) at progress (/Users/xaviergeerinck/conductor/workspaces/main/cairo/apps/analytics/node_modules/.vite/deps_ssr/react-server-dom-webpack_client__edge.js:5015:20) ``` ## Suggestion The proper fix should happen at two levels: **1. Vinext/`@vitejs/plugin-rsc` should set up the React dispatcher before processing RSC streams** Next.js does this internally — before calling into `react-server-dom-webpack/client.edge` to process RSC streams in SSR, it ensures `ReactSharedInternals.H` is initialized. Vinext's SSR runner should do the same. The fix would be in the SSR entry point that calls `createFromFetch`/`createFromReadableStream`, wrapping it inside a React render context (or manually setting up a minimal dispatcher). **2. Vinext should auto-detect missing `"use client"` directives** Many packages (next-themes, sonner, better-auth/react) ship dist files without `"use client"` because Next.js infers it from the module graph. Vinext could: - **Option A**: Provide a config option like `clientModules: [/next-themes/, /sonner/]` that tells the RSC plugin to treat matching modules as client boundaries - **Option B**: Heuristically detect modules that import React hooks (`useState`, `useContext`, etc.) and auto-mark them as client modules in the RSC environment - **Option C**: Document a recommended `injectClientDirectives` plugin pattern for users to handle this themselves
Author
Owner

@ryanbuening commented on GitHub (Feb 25, 2026):

I'm possibly seeing a similar issue with NextAuth:

[vinext] Route handler error: TypeError: Cannot read properties of undefined (reading 'searchParams')
    at NextAuthRouteHandler (.\node_modules\next-auth\next\index.js:55:48)
    at async NextAuth._args$ (.\node_modules\next-auth\next\index.js:89:16)
    at async _handleRequest (virtual:vinext-rsc-entry:1564:26)
    at async eval (virtual:vinext-rsc-entry:1218:24)
<!-- gh-comment-id:3961509751 --> @ryanbuening commented on GitHub (Feb 25, 2026): I'm possibly seeing a similar issue with NextAuth: ```shell [vinext] Route handler error: TypeError: Cannot read properties of undefined (reading 'searchParams') at NextAuthRouteHandler (.\node_modules\next-auth\next\index.js:55:48) at async NextAuth._args$ (.\node_modules\next-auth\next\index.js:89:16) at async _handleRequest (virtual:vinext-rsc-entry:1564:26) at async eval (virtual:vinext-rsc-entry:1218:24) ```
Author
Owner

@threepointone commented on GitHub (Feb 26, 2026):

I think we need to solve this in @vitejs/plugin-rsc

<!-- gh-comment-id:3966179956 --> @threepointone commented on GitHub (Feb 26, 2026): I think we need to solve this in `@vitejs/plugin-rsc`
Author
Owner

@liuxiaopai-ai commented on GitHub (Feb 26, 2026):

Update: a fix is up in PR #118 and now linked as Fixes #50.

https://github.com/cloudflare/vinext/pull/118

@threepointone @southpolesteve could you take a look when you have a moment?

Validation summary (on the PR branch):

  • pnpm -s run lint
  • pnpm -s run typecheck
  • pnpm -s test tests/app-router.test.ts
  • pnpm -s test tests/static-export.test.ts
  • pnpm -s test tests/ecosystem.test.ts
  • full pnpm -s test passed (47 files / 1820 passed / 3 skipped)
<!-- gh-comment-id:3968532008 --> @liuxiaopai-ai commented on GitHub (Feb 26, 2026): Update: a fix is up in PR #118 and now linked as `Fixes #50`. https://github.com/cloudflare/vinext/pull/118 @threepointone @southpolesteve could you take a look when you have a moment? Validation summary (on the PR branch): - `pnpm -s run lint` - `pnpm -s run typecheck` - `pnpm -s test tests/app-router.test.ts` - `pnpm -s test tests/static-export.test.ts` - `pnpm -s test tests/ecosystem.test.ts` - full `pnpm -s test` passed (47 files / 1820 passed / 3 skipped)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#13
No description provided.