Fix duplicate waku instances in docs build - #204
Merged
Conversation
deroll.dev rendered every page and then went blank a moment later. The
server-rendered HTML is fine — the client bundle throws during hydration:
Error: Missing Router
at O (assets/client-CLCqVXAf.js) <- waku/router/client
at C (assets/Head-C7z0VYp5.js) <- vocs' <Head>
React unmounts the root when hydration throws, so the page paints and
disappears. Nothing fails at build time and SSR still returns 200, which
is why the previous fix looked like it had worked.
The client bundle contained waku's router runtime twice. vocs declares
waku as a peer dependency and apps/docs satisfies it, so the two resolve
waku independently, and under bun's isolated linker a package gets a
separate copy per distinct dependency graph. The graphs diverged on
`tsx`: the root package.json asks for ^4.23.12 while the lockfile pinned
`vocs/tsx` at 4.23.1. `tsx` is an optional peer of vite, so that split
vite@8.2.1 into two copies, which split waku into two copies, which put
two waku router modules in the bundle. vocs' <Head> then read its router
context from one copy while the <Router> provider came from the other.
Pin tsx to a single resolution with a root `overrides` entry: one tsx,
one vite@8.2.1, one waku, one router context.
Add apps/docs/scripts/check-single-waku.mjs, run from the docs `build`
and `dev` scripts, so a future split fails the build instead of shipping
a site that blanks out in the browser. Both the unrelated-optional-peer
mechanism and the fix are documented in the script.
Verified with headless Chromium against a local `vocs build` served by
dist/serve-node.js: before, /, /app/quick-start, /app/wallet, /explorer
and the rest all went from rendered text to 0 characters with "Missing
Router"; after, every route keeps its content, hydrates (the search hint
switches from ⌘K to Ctrl K) and client-side navigation works. The guard
exits 1 on the pre-fix lockfile and 0 after.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkuwpoMdwT9heR9MUTAiFV
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
PR #204's docs preview failed the new guard, with the two waku copies carrying the same store hashes as before the dedupe: from this app: .../waku@1.0.0-beta.8+ad2d21bfe8d1fed2/... from vocs: .../waku@1.0.0-beta.8+e5046fa2a97318d0/... The `tsx` override is correct — a clean install resolves one waku — but `bun install` does not re-link packages it already considers satisfied. Vercel restores node_modules from its build cache, so the tree built before the dedupe survived the install untouched and the docs app kept resolving its own stale waku copy. Reproduced exactly: clean-install the pre-fix package.json/lockfile (two copies), restore the fixed pair, then `bun install --frozen-lockfile` without deleting node_modules — both copies persist, the same way they did on Vercel. Adding --force re-links and the guard passes. --frozen-lockfile is still enforced alongside --force: an out-of-date lockfile still fails with "lockfile had changes, but lockfile is frozen". So set the docs project's install command in apps/docs/vercel.json. CI is unaffected — it installs into an empty tree, which is why it went green while the deploy did not. Also spell out the stale-tree case in the guard's error message, since it is the failure mode someone is most likely to hit again. Verified from scratch with the committed lockfile: one waku copy, one waku router chunk in the client bundle, and headless Chromium finds /, /app/quick-start, /app/wallet, /genext2fs and /explorer all keeping their content through hydration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkuwpoMdwT9heR9MUTAiFV
…d it The root package.json listed `tsx` in `dependencies`, but nothing at the root runs it — no script, no turbo task, no workflow. The packages that do run tsx already declare it themselves (apps/examples for its `dev:*` scripts, @deroll/create-app for `start`). That stray declaration is what split waku in two. `tsx` is an optional peer of vite, so a root-level tsx that differed from vocs' copy gave vite@8.2.1 two variants, which gave waku two variants, which put two router contexts in the docs client bundle and blanked every page on hydration. Deleting it fixes the split at the source, so the `overrides` entry that pinned tsx is no longer needed either — with both gone a clean install resolves one tsx, one vite@8.2.1 and one waku, and the lockfile change is a pure deletion with no other churn. Also drop apps/docs/scripts/check-single-waku.mjs and restore the plain `vocs build` / `vocs dev` scripts. It was a guard against a divergence that no longer has a cause here, and it isn't worth carrying. apps/docs/vercel.json stays, and now carries the whole load: `bun install` does not re-link a tree it already considers satisfied, so without --force Vercel's restored build cache would keep serving the pre-dedupe node_modules no matter how correct the lockfile is. That is exactly how the split reached production, and nothing else in the repo prevents it. Verified from a clean install: one waku copy, one waku router chunk in the client bundle, `bun run build` and `bun run lint` green, and headless Chromium finds /, /app/quick-start, /app/application, /app/wallet, /app/vouchers, /app/migrating, /genext2fs and /explorer all keeping their content through hydration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkuwpoMdwT9heR9MUTAiFV
Removing the unused root `tsx` dependency fixes the waku split at its source, so the only thing --force still bought was protection against Vercel's build cache restoring a node_modules that predates the fix. That is a one-time concern, handled by a redeploy without build cache, and not worth a config file. Note for whoever merges this: the `deroll` project's production cache still holds the pre-fix tree from c552f33. `bun install` does not re-link a tree it already considers satisfied — a lockfile change alone did not invalidate it on PR #204 — so the first prerelease/v2 build after this merge must be a "Redeploy without Build Cache", or deroll.dev will come back blank. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkuwpoMdwT9heR9MUTAiFV
tuler
pushed a commit
that referenced
this pull request
Aug 13, 2026
PR #204's docs preview failed the new guard, with the two waku copies carrying the same store hashes as before the dedupe: from this app: .../waku@1.0.0-beta.8+ad2d21bfe8d1fed2/... from vocs: .../waku@1.0.0-beta.8+e5046fa2a97318d0/... The `tsx` override is correct — a clean install resolves one waku — but `bun install` does not re-link packages it already considers satisfied. Vercel restores node_modules from its build cache, so the tree built before the dedupe survived the install untouched and the docs app kept resolving its own stale waku copy. Reproduced exactly: clean-install the pre-fix package.json/lockfile (two copies), restore the fixed pair, then `bun install --frozen-lockfile` without deleting node_modules — both copies persist, the same way they did on Vercel. Adding --force re-links and the guard passes. --frozen-lockfile is still enforced alongside --force: an out-of-date lockfile still fails with "lockfile had changes, but lockfile is frozen". So set the docs project's install command in apps/docs/vercel.json. CI is unaffected — it installs into an empty tree, which is why it went green while the deploy did not. Also spell out the stale-tree case in the guard's error message, since it is the failure mode someone is most likely to hit again. Verified from scratch with the committed lockfile: one waku copy, one waku router chunk in the client bundle, and headless Chromium finds /, /app/quick-start, /app/wallet, /genext2fs and /explorer all keeping their content through hydration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkuwpoMdwT9heR9MUTAiFV
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a build-time check to prevent duplicate
wakupackage instances from being bundled in the docs app, which was causing hydration failures in the browser.Problem
The docs app uses
vocs, which declareswakuas a peer dependency. Under bun's isolated linker, divergences in dependency graphs between the app and vocs (such as different versions of transitive dependencies liketsx) can result in two separate physical copies ofwakubeing resolved. Both copies end up in the client bundle, causing vocs' components to read from one waku router context while theRouterprovider comes from another, resulting in "Missing Router" hydration errors.Changes
apps/docs/scripts/check-single-waku.mjs): Validates thatwakuresolves to a single physical copy by comparing the resolved paths from both the app and vocs. Fails the build with a helpful error message if duplicates are detected.apps/docs/package.json): Integrated the check intobuildanddevcommands to catch the issue early.package.json): Addedtsxoverride to ensure consistent resolution across dependency graphs and prevent future divergence.Implementation Details
The check uses Node's
createRequire()to resolvewaku/package.jsonfrom both the app's and vocs' own node_modules contexts. If the resolved paths differ, it exits with a detailed error message guiding developers to identify and pin the offending dependency.https://claude.ai/code/session_01FkuwpoMdwT9heR9MUTAiFV