Skip to content

refactor(solid-router): move off sharedConfig onto Solid 2.0 public hydration APIs (solid-js rc.13) - #8569

Open
ryansolid wants to merge 2 commits into
TanStack:solid-router-v2-prefrom
ryansolid:solid-router-public-hydration-api
Open

ryansolid wants to merge 2 commits into
TanStack:solid-router-v2-prefrom
ryansolid:solid-router-public-hydration-api

Conversation

@ryansolid

Copy link
Copy Markdown

Why

In the Solid 2.0 RC, sharedConfig (and everything reachable from solid-js/internal) is internal runtime state: its shape changes between RCs without notice, and reads of the raw _$HY registry depend on the wire format. @tanstack/solid-router was reading both for its native SSR match transfer and for useHydrated. Solid now ships a small public hydration API for exactly these jobs, in solid-js / @solidjs/web 2.0.0-rc.13 (solidjs/solid#3718):

// solid-js
isHydrating(): boolean   // true only inside the active hydration claim window
isHydratable(): boolean  // false under <NoHydration>, true again inside nested <Hydration>
// @solidjs/web
getHydrationWriter(): HydrationWriter | undefined                // server: the current render's keyed value channel
takeHydrationValue<T>(key): HydrationValue<T> | undefined         // client: read-and-delete a keyed value

This PR bumps the Solid packages to rc.13 and moves the router onto that API. After it, no Solid package in this repo reads sharedConfig, imports solid-js/internal, or touches _$HY.

Survey

git grep -n -E "sharedConfig|solid-js/internal|_\$HY|noHydrate" across the Solid packages:

Site Use Now
solid-router/src/ClientOnly.tsx Solid.sharedConfig.hydrating isHydrating()
solid-router/src/registryTransfer.ts (server) sharedConfig.context.serialize, context.noHydrate guard getHydrationWriter() + isHydratable()
solid-router/src/registryTransfer.ts (client) raw _$HY.r reads, tsr: prefix scan, delete, s/v unwrapping takeHydrationValue() per match
solid-router/tests/matches-hydration-boundary.test.tsx sharedConfig.hydrating = false reset removed (nothing sets it)
solid-router/repro-external-ssr/entry-client.tsx enumerates _$HY.r for tsr: keys takeHydrationValue + server-side assertion
solid-start/src/plugin/vite.ts comment mentioning _$HY.done unchanged (describes why the runtime is deduped; still accurate)

getRequestEvent / provideRequestEvent were already on the public @solidjs/web exports; nothing to change there.

Changes

packages/solid-router/src/registryTransfer.ts

  • Server (serializeMatchTransfer): sharedConfig.context.serialize(key, v) → getHydrationWriter()?.write(key, v), gated on isHydratable(). The old ctx.noHydrate guard was dead code in 2.0 (the field no longer exists), so match transfer is now skipped under <NoHydration>, where it was previously written.
  • Client (primeRouterFromRegistry): the raw _$HY.r read, the tsr:-prefix pre-scan, the manual delete, and the s/v settled-promise unwrapping are replaced by takeHydrationValue(MATCH_KEY_PREFIX + match.id) per matched route. Only status: 'resolved' entries prime; pending / rejected / missing fall through to the existing boot, as before.

packages/solid-router/src/ClientOnly.tsx

  • useHydrated: globalHydrated && !sharedConfig.hydrating → globalHydrated && !isHydrating().

Tests and repro

  • tests/matches-hydration-boundary.test.tsx: drop the sharedConfig.hydrating = false reset.
  • tests/observe-navigation.test.tsx: rc.13 replaced attribution.navigations() with attribution.history('navigation') (a Solid API change in this RC range, not part of the hydration work).
  • repro-external-ssr/: the client no longer enumerates _$HY.r. The runner asserts that the server shell chunk carries tsr: entries, and the client asserts every committed match's entry was taken (takeHydrationValue returns undefined). The runner also copies TextEncoder/TextDecoder into jsdom, because rc.13's @solidjs/web server-function protocol module creates its event-stream heartbeat at module scope and jsdom has no encoding globals.

Dependency and peer-dependency changes

  • solid-js, @solidjs/web: ^2.0.0-rc.9 → ^2.0.0-rc.13 in every package, example, e2e app, and benchmark that declared them.
  • @solidjs/vite-plugin: ^3.0.0-next.44 → ^3.0.0-next.46. next.44 locked @solidjs/compiler at rc.9; next.46's range resolves to rc.13, keeping the JSX compiler on the runtime's RC.
  • @solidjs/babel-plugin (webpack quickstart) and the @rsbuild/plugin-solid>@solidjs/{compiler,babel-plugin} overrides in pnpm-workspace.yaml: 2.0.0-rc.9 → 2.0.0-rc.13.
  • Peer floors raised to rc.13 (the router needs the new exports):
    • @tanstack/solid-router, solid-start, solid-start-client, solid-start-server: solid-js / @solidjs/web >=2.0.0-0 <3.0.0 (start: web >=2.0.0-rc.6) → >=2.0.0-rc.13 <3.0.0
    • @tanstack/solid-router-ssr-query: >=2.0.0-beta.17 → >=2.0.0-rc.13
    • @tanstack/solid-router-devtools: ^2.0.0-rc.9 → ^2.0.0-rc.13
  • Lockfile: the only package-set changes are the Solid packages above (plus two @emnapi entries dropped with the rc.9 compiler).

No public exports of the TanStack packages change.

Behavior notes

  • <NoHydration>: settled match state is no longer serialized under <NoHydration> (see above).
  • useHydrated initial value: sharedConfig.hydrating stayed true after the root hydration pass while streamed boundaries were still pending, so a component created client-side in that window started with hydrated() === false. isHydrating() is true only inside a claim window (the root pass, or a streamed boundary resuming), so such a component now starts true. Components inside a resuming boundary still start false. GenericHydrate (solid-start-client) reads this at creation; the deferred-hydration e2e passes.
  • Client boot cost: the old code pre-scanned the registry for any tsr: key before running matchRoutes. There's intentionally no API to enumerate keys, so the boot now runs one pure matchRoutes pass on every client router construction (SPA and Start included) and falls through at the first missing key. That pass calls user validateSearch / loaderDeps / params.parse once more on those boots.
  • Hydration timing: nothing here waits for full-page hydration. There's no onHydrationEnd / _$HY.done / sharedConfig.done dependence; useHydrated flips on its first effect run, after the root pass.

Validation (published solid-js / @solidjs/web / @solidjs/signals 2.0.0-rc.13)

A single copy of solid-js, @solidjs/web, and @solidjs/signals resolves from every Solid package.

  • @tanstack/solid-router: tsc -p tsconfig.legacy.json clean; vitest (jsdom) 889 passed / 2 skipped; vitest --mode server 7 passed; ESLint clean on the changed files; vite build OK.
  • repro-external-ssr (renderToStream → jsdom hydrate): PASS. tsr: entries were in the shell chunk, the router was primed at construction with 0 loader runs before and after hydrate, the server node was reused, there was no pending flash, deferred data streamed, and post-hydration navigation showed pending UI.
  • solid-start-client (tsc + 8 tests), solid-start-server (tsc + vitest), solid-router-ssr-query (tsc + vitest), solid-router-devtools (tsc): all pass.
  • e2e (Playwright, chromium): e2e/solid-start/basic 80 passed / 4 skipped; e2e/solid-start/deferred-hydration (Vite) 15 passed.
  • Also validated before publish against local core builds of the API commit and the "don't wait for hydration" fix: the same results, except the attribution test (which predates the history() rename).

Not run locally: the rsbuild variants, and the full e2e matrix.

On behalf of @ryansolid.

— Claude via Cursor

ryansolid and others added 2 commits September 30, 2026 00:13
….0.0-next.46

Bump solid-js and @solidjs/web to ^2.0.0-rc.13 across the monorepo,
@solidjs/vite-plugin to ^3.0.0-next.46 (its compiler range resolves to
rc.13, keeping the JSX compiler on the runtime's RC), the webpack
example's @solidjs/babel-plugin and the @rsbuild/plugin-solid compiler
overrides to 2.0.0-rc.13, and raise the Solid packages' solid-js /
@solidjs/web peer floors to 2.0.0-rc.13.

rc.13 replaced attribution.navigations() with
attribution.history('navigation'); observe-navigation.test.tsx follows.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…olid 2.0 APIs

Replace solid-js internals with the public hydration surface shipped in
solid-js / @solidjs/web 2.0.0-rc.13 (solidjs/solid#3718):

- useHydrated: sharedConfig.hydrating -> isHydrating()
- serializeMatchTransfer: sharedConfig.context.serialize ->
  getHydrationWriter().write, gated on isHydratable() (the old
  context.noHydrate guard was dead in 2.0)
- primeRouterFromRegistry: raw _$HY.r reads, the tsr: prefix scan and
  s/v unwrapping -> per-match takeHydrationValue()
- tests / external-SSR repro: drop the sharedConfig reset and _$HY
  enumeration; the repro asserts entries server-side and consumption
  through takeHydrationValue

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 2c171aac-18d8-453d-86a0-061ddba8b3bb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​solidjs/​web@​2.0.0-rc.9 ⏵ 2.0.0-rc.13100 +11008397 +1100
Updated@​solidjs/​babel-plugin@​2.0.0-rc.9 ⏵ 2.0.0-rc.1392 +6100100 +195 +2100
Updatedsolid-js@​2.0.0-rc.9 ⏵ 2.0.0-rc.1310010095 +196100
Updated@​solidjs/​vite-plugin@​3.0.0-next.44 ⏵ 3.0.0-next.4696 -1100100 +196 +1100

View full report

@socket-security

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Priority Alert  (click "▶" to expand/collapse) Action
Low priority
Low adoption: npm @solidjs/compiler-darwin-x64

Location: Package overview

From: pnpm-lock.yaml → npm/@rsbuild/plugin-solid@2.0.0-rc.0 → npm/@solidjs/vite-plugin@3.0.0-next.46 → npm/@solidjs/compiler-darwin-x64@2.0.0-rc.13

ℹ Read more on: This package | This alert | What are unpopular packages?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Unpopular packages may have less maintenance and contain other problems.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@solidjs/compiler-darwin-x64@2.0.0-rc.13. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn

View full report

This branch has not been deployed

No deployments
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.

1 participant