Skip to content

fix(helper): self-removing bootstrap script avoids React hydration mismatch#121

Merged
peter-adam-dy merged 3 commits into
mainfrom
fix-react-hydration-problem
May 25, 2026
Merged

fix(helper): self-removing bootstrap script avoids React hydration mismatch#121
peter-adam-dy merged 3 commits into
mainfrom
fix-react-hydration-problem

Conversation

@peter-adam-dy

Copy link
Copy Markdown
Collaborator

Summary

  • Bootstrap <script> injected by veld_inject lands between <!DOCTYPE> and <html>. HTML5 parser relocates it into <head>. Next.js app-router hydrates from the <html> root and reports a hydration mismatch because the script is not in the React tree.
  • Fix: the bootstrap IIFE removes its own <script> tag via document.currentScript.parentNode.removeChild(...) before returning. Runs synchronously during script execution, before React hydration walks the live DOM.
  • Side effects survive removal: console interception + error listeners attach to window/console; the dynamically-loaded client-log.js and feedback/script.js are scheduled via requestIdleCallback and use document.head.appendChild, independent of the bootstrap tag's presence.

Why this is safe

  • document.currentScript is non-null during synchronous inline script execution.
  • The __veld_cl dedup guard sits on window, not the DOM, so re-injection on the same page is still a no-op.
  • Removal is null-guarded: if(s&&s.parentNode).
  • No effect on non-React apps — they don't care that an inline script tag disappeared after running.

Workaround removed

Previously users had to use next/script with strategy="beforeInteractive" to dodge the mismatch. No longer needed.

Test plan

  • cargo test -p veld-helper caddy::tests::test_bootstrap — 13 passed (includes new test_bootstrap_script_self_removes covering all 3 feature combos)
  • cargo build -p veld-helper clean
  • Manual smoke: run veld in front of a Next.js app-router project, confirm no hydration mismatch warning in console and overlay/client-log still load

…n mismatch

The injected bootstrap <script> is prepended right after <!DOCTYPE>, which
the HTML5 parser relocates into <head>. Next.js app-router (and any other
framework that hydrates from the <html> root) sees an extra child not
present in the React tree and reports a hydration mismatch.

Fix: have the bootstrap script remove its own <script> tag from the DOM at
the end of the IIFE. `document.currentScript` is set during synchronous
script execution, so removal runs before hydration. Side effects (console
interception, error listeners, and the requestIdleCallback-deferred asset
loads for client-log.js and feedback/script.js) survive removal because
they're attached to window/console, not the script element.
New clippy lint in latest stable flags `sort_by(|a,b| a.x.cmp(&b.x))`
on a Copy key — `sort_by_key(|a| a.x)` is equivalent and idiomatic.
Unrelated to the hydration fix but blocks CI.
@peter-adam-dy peter-adam-dy marked this pull request as ready for review May 25, 2026 07:07
@peter-adam-dy peter-adam-dy merged commit 9f156fa into main May 25, 2026
16 checks passed
@peter-adam-dy peter-adam-dy deleted the fix-react-hydration-problem branch May 25, 2026 07:07
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