Search code examples
reactjsastrojshcaptcha

Astro is not rendering hCaptcha after hydrating the component


I'm having issues rendering hCaptcha on my Astro project. The Captcha field appears when refreshing the page for a split second and disappears.

index.astro

---
import Hcaptcha from "../components/hcaptcha.jsx";
---

<!doctype html>
<html lang="en">
  <head>
    <script src="https://js.hcaptcha.com/1/api.js" async defer></script>
  </head>
  <body>
    <Hcaptcha client:load />
  </body>
</html>

hcaptcha.jsx

import React from 'react';

const Hcaptcha = () => {
  return (
    <section className="websiteTitle__captcha">
      <div
        className="h-captcha"
        id="signupCaptcha"
        data-sitekey={import.meta.env.PUBLIC_MY_KEY}
        data-size="normal"
        data-theme="dark"
      ></div>
    </section>
  );
};

export default Hcaptcha;

astro.config.mjs

import { defineConfig } from "astro/config";
import react from "@astrojs/react";

import node from "@astrojs/node";

// https://astro.build/config
export default defineConfig({
  integrations: [react()],
  vite: {
    server: {
      watch: {
        usePolling: true,
      },
    },
  },
  output: "hybrid",
  adapter: node({
    mode: "standalone",
  }),
});

I'm getting the following warnings in Developer Tools in browser:

Warning: Did not expect server HTML to contain a <iframe> in <div>.

Warning: An error occurred during hydration. The server HTML was replaced with client content in <astro-island>.

Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.

Uncaught Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.

Here's a live preview on StackBlitz to try it out.

Removing client:load fixes the issue, and renders Captcha, but since it's not hydrated I can't really use it.

Tried switching from SSG to SSR, didn't make a difference. I'm running this code locally on WSL.


Solution

  • Most probably what's happening is:

    1. Component is server-side-rendered and the HTML sent to the client
    2. The h-captcha script is executed and modifies the HTML (inserting an iframe for example).
    3. React tries to hydrate the component, expecting the original HTML from the server, but finds an iframe instead. That's how you get Warning: Did not expect server HTML to contain a <iframe> in <div>.

    So long story short, h-captcha script isn't intended to be used with React without any wrapper.

    Either render the HTML only on the server, or if you actually need some interactivity, use something like react-hcaptcha.