Skip to content

fix(atproto-comments): ship a Svelte-external build to fix hydration crash (#7)#8

Merged
Theo-Steiner merged 2 commits into
mainfrom
fix/atproto-comments-dual-svelte-runtime
Jul 24, 2026
Merged

fix(atproto-comments): ship a Svelte-external build to fix hydration crash (#7)#8
Theo-Steiner merged 2 commits into
mainfrom
fix/atproto-comments-dual-svelte-runtime

Conversation

@Theo-Steiner

Copy link
Copy Markdown
Contributor

Fixes #7.

Root cause

@svebcomponents/atproto.comments's client build bundled its own Svelte runtime. In a SvelteKit/Vite app — which has its own Svelte — that put two Svelte runtimes on the page. Under experimental.async, the app's async-boundary hydration (#hydrate_resolved_contentlegacy-client) crossed into the component's separate runtime and dereferenced a null effect:

TypeError: Cannot read properties of null (reading 'f')

The correctly server-rendered thread was then wiped on the client, leaving only the header. The crash only surfaced through the async wrapper path (the documented self-fetch SSR tier), which is why smaller/synchronous setups didn't hit it.

Fix

The build already supports a Svelte-external variant (@svebcomponents/build ≥ 0.3.1: externalSvelte + pluginDedupe + the hydration-host bundling); this component just never opted in.

  • package.json — declare a svelte export condition on . and ./ssr, pointing at new dist/client-svelte / dist/server-svelte builds.
  • svebcomponents.config.ts — emit those Svelte-external variants and apply the vendored direct-props HydrationHost to every build variant (matched by outDir/entry, not array index).

Bundler consumers (SvelteKit, Vite) resolve the svelte condition and dedupe the component onto the app's single Svelte runtime, so the crash is gone by construction — there is only one runtime, so there is no cross-runtime effect to corrupt. The default dist/client (Svelte bundled) is unchanged and still serves the CDN / bare-import drop-in, which has no app Svelte to dedupe against.

Verification

  • Component: client-svelte/server-svelte externalize Svelte; the vendored host is compiled into every variant; test (11), svelte-check (0 errors), publint, and eslint all pass.
  • Real consumer (new-theosteiner.de, the reporter's app, tested via a packed tarball): the duplicate runtime is eliminated — the optimized component dep went from 2 bundled-runtime markers to 0 and now imports the app's shared runtime-*.js chunk. The page still hydrates: 227 → 227 comments, zero page errors.
  • Workspace host app: existing hydration e2e still passes.

Consumer follow-up

After publish, consumers just pnpm update @svebcomponents/atproto.comments — no app changes, and any ssr.noExternal entry for this component can be dropped.

🤖 Generated with Claude Code

Theo-Steiner and others added 2 commits July 25, 2026 01:53
…crash

The client build bundled its own Svelte runtime. In a SvelteKit/Vite app —
which has its own Svelte — that put two Svelte runtimes on the page, and under
`experimental.async` the app's async-boundary hydration would cross into the
component's separate runtime and dereference a null effect, crashing with
"Cannot read properties of null (reading 'f')" and wiping a correctly
server-rendered thread (#7).

Emit `dist/client-svelte`/`dist/server-svelte` (Svelte marked external) and
declare a `svelte` export condition pointing at them, so bundler consumers
dedupe the component onto the app's single Svelte runtime. The default
`dist/client` (Svelte bundled) is unchanged and still serves the CDN /
bare-`import` drop-in, which has no app Svelte to dedupe against. The vendored
direct-props HydrationHost is now applied to every build variant (matched by
outDir/entry rather than array index).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9oixH2t2npbf9u3ara93J
@Theo-Steiner
Theo-Steiner merged commit a8212eb into main Jul 24, 2026
1 check passed
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.

Hydration crash ("Cannot read properties of null (reading 'f')") wipes a correctly-SSR'd large/deep thread

1 participant