Skip to content

fix(web): send without a microtask when prepareRequest is sync - #3988

Closed
brenelz wants to merge 1 commit into
solidjs:nextfrom
brenelz:fix/prepare-request-sync-3959
Closed

brenelz wants to merge 1 commit into
solidjs:nextfrom
brenelz:fix/prepare-request-sync-3959

Conversation

@brenelz

@brenelz brenelz commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #3959. With any prepareRequest hook configured, even init => init, a server-function read that hydration adopted was fetched again from the browser.

Hydration runs an adopted source's compute once inside subFetch, which swaps window.fetch for a never-settling mock only for that synchronous call. createRequest always awaited the hook, so even a synchronous hook pushed send(base, init) one microtask later, after the real fetch was restored, and the request reached the network.

createRequest now awaits the hook's result only when it is a thenable. A synchronous hook sends in the calling tick, like the transport does without a hook. An async hook is still awaited and keeps its current behavior; the prepareRequest docs now say that an async hook makes hydrated reads fetch again.

Left open: calls whose arguments need the rich-argument codec still await serializeArguments(args) before sending (the POST path in initializeResponse and the GET path in fetchServerFunction), so a hydrated read with such arguments refetches with or without a hook. Fixing that needs synchronous serialization, which is a larger change.

How did you test this change?

  • test/server/server-functions-extensions.spec.tsx adds a case that checks a synchronous hook sends before the call returns, with and without arguments, and that an async hook still sends after the await. It fails on next (0 synchronous sends) and passes with the fix: pnpm build in packages/web, then npx vitest run --config vite.config.server.mjs test/server/server-functions-extensions.spec.tsx.
  • test/hydration/prepare-request-hydration-3959.spec.tsx hydrates a createMemo(() => read()) over adopted data, once without a hook and once with init => init, and asserts no request is sent. On next the hook case sends /_server/data/prepare-request-3959; with the fix neither case sends anything: npx vitest run --config vite.config.hydrate.mjs test/hydration/prepare-request-hydration-3959.spec.tsx.
  • I ran the @solidjs/web client, server and hydration suites before and after the change. The local compiler build is older than Client element claims fire before dynamic attributes are applied, and only href/action writes re-claim #3923 and Select loses its bound value when options load asynchronously #3928, so some element-claim and server specs fail on next as well; the failing test lists are identical before and after, apart from the new test.
  • node size.mjs in scripts/size: the server-function scenarios pass. "app: CSR, observe tier" is over its cap by 5 B, with the same byte count on next.

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Oct 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 84ee3a5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@solidjs/web Patch
@solidjs/babel-plugin Patch
@solidjs/diagnostics Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
test-integration Patch
todos-server-example Patch
@solidjs/compiler Patch
@solidjs/signals Patch
solid-js Patch
@solidjs/universal Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codspeed

codspeed Bot commented Oct 12, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️ 2 benchmarks spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 195 untouched benchmarks


Comparing brenelz:fix/prepare-request-sync-3959 (84ee3a5) with next (84dada7)

Open in CodSpeed

Copy link
Copy Markdown
Member

Review: one type fix, then ready.

The job check fails in @solidjs/web#test-types:

test/hydration/prepare-request-hydration-3959.spec.tsx(9,58): error TS2305: Module '"solid-js"' has no exported member 'sharedConfig'.

sharedConfig comes from solid-js/internal, as in the other hydration specs:

import { createMemo, createRoot, enableHydration, flush } from "solid-js";
import { sharedConfig } from "solid-js/internal";

With that change test-types is clean and the spec still passes.

The fix itself is right. createRequest reaches the hook with no await in between, so a synchronous return now sends within the subFetch window, and a thenable is still awaited. I checked it locally on 84ee3a5, with web rebuilt:

Check Result
Both new tests with server-functions/src/client.ts reverted to next fail
Both new tests on the PR pass (13/13 server, 2/2 hydration)

The gap you left open is worth its own issue: a hydrated read whose arguments go through the rich-argument codec still refetches, because of await serializeArguments(args), with or without a hook.

— Claude via Claude Code


Generated by Claude Code

Copy link
Copy Markdown
Member

Merged through #3990. This session could not push to your fork, so your commit went in unchanged and still authored by you (c2d4571 on next), followed by the solid-js/internal import fix for test-types (4101c67). Closing this one as superseded. Thanks!

— Claude via Claude Code


Generated by Claude Code

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants