Skip to content

Fix hydration mismatches in FeedbackForm (GoogleReCaptchaWidget, FormHeader)#11

Open
mamico wants to merge 2 commits into
mainfrom
fix/hydration-mismatch
Open

Fix hydration mismatches in FeedbackForm (GoogleReCaptchaWidget, FormHeader)#11
mamico wants to merge 2 commits into
mainfrom
fix/hydration-mismatch

Conversation

@mamico

@mamico mamico commented Jul 13, 2026

Copy link
Copy Markdown
Member

Summary

  • GoogleReCaptchaWidget branched on __CLIENT__ directly in its render output, so the server always rendered nothing while the client's first hydration pass rendered the whole provider/grid/recaptcha subtree - a guaranteed mismatch on every page that mounts this widget. Fixed by rendering nothing on both the server and the client's first render, mounting the real widget only after hydration completes via an effect.
  • FormHeader was lazy-loaded via @loadable/component without being registered in Volto's own SSR chunk-extraction (config.settings.loadables), so the server rendered its content immediately while the client's first hydration pass could render nothing until its chunk finished loading. It's a tiny, always-needed component (a div + span), so there's no benefit to code-splitting it - fixed by importing it directly.

Both were found while debugging a hydration mismatch that appeared on every page of a Volto 18.35 site using this addon (since FeedbackForm mounts globally in the footer).

Test plan

  • Confirm no hydration warnings/errors on a page load with FeedbackForm mounted, with and without RAZZLE_RECAPTCHA_KEY configured
  • Confirm the rating form's answer/comment steps still render and submit correctly

mamico added 2 commits July 13, 2026 09:09
   reCAPTCHA needs window/document to inject Google's script, so it can't
   render during SSR. The component branched on __CLIENT__ directly in its
   render output, so the server always rendered nothing while the client's
   first hydration pass rendered the whole provider/grid/recaptcha subtree
   in the same spot - a guaranteed mismatch on every page that mounts this
   widget. Render nothing on both the server and the client's first render,
   and only mount the real widget after hydration completes via an effect.
FormHeader is a tiny, always-needed component (a div + span). Lazy-loading
it via @loadable/component caused a guaranteed hydration mismatch: unlike
properly-registered loadables (see config.settings.loadables), this bare
loadable() call isn't tracked by Volto's own SSR chunk extraction, so the
server rendered it immediately while the client's first hydration pass rendered nothing until its chunk finished loading. There's no benefit to
code-splitting something this small, so import it directly instead.
@mamico mamico requested review from giuliaghisini and pnicolli July 13, 2026 07:13
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