remix: React 18 : Hydration failed because the initial UI does not match what was rendered on the server.

What version of Remix are you using?

1.3.4

Steps to Reproduce

Update https://github.com/remix-run/indie-stack/tree/main

  • react: 18.0.0
  • react-dom: 18.0.0

Update entry.client.tsx :

import { hydrateRoot } from "react-dom/client";
import { RemixBrowser } from "remix";

hydrateRoot(document, <RemixBrowser />);

Run dev with NODE_ENV=production

Expected Behavior

No error

Actual Behavior

White screen with errors :

Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.
    at throwOnHydrationMismatch (:3000/build/entry.client-K3EESZRH.js:10325:17)
    at tryToClaimNextHydratableInstance (:3000/build/entry.client-K3EESZRH.js:10335:15)
    at updateHostComponent$1 (:3000/build/entry.client-K3EESZRH.js:14601:13)
    at beginWork (:3000/build/entry.client-K3EESZRH.js:15679:22)
    at HTMLUnknownElement.callCallback2 (:3000/build/entry.client-K3EESZRH.js:3453:22)
    at Object.invokeGuardedCallbackDev (:3000/build/entry.client-K3EESZRH.js:3478:24)
    at invokeGuardedCallback (:3000/build/entry.client-K3EESZRH.js:3512:39)
    at beginWork$1 (:3000/build/entry.client-K3EESZRH.js:18903:15)
    at performUnitOfWork (:3000/build/entry.client-K3EESZRH.js:18367:20)
    at workLoopSync (:3000/build/entry.client-K3EESZRH.js:18307:13)
Warning: An error occurred during hydration. The server HTML was replaced with client content in <#document>.
Uncaught DOMException: Failed to execute 'appendChild' on 'Node': Only one element on document allowed.
    at appendChildToContainer (http://192.168.1.17:3000/build/entry.client-K3EESZRH.js:7946:24)
    at insertOrAppendPlacementNodeIntoContainer (http://192.168.1.17:3000/build/entry.client-K3EESZRH.js:16695:15)
    at insertOrAppendPlacementNodeIntoContainer (http://192.168.1.17:3000/build/entry.client-K3EESZRH.js:16702:15)
    at insertOrAppendPlacementNodeIntoContainer (http://192.168.1.17:3000/build/entry.client-K3EESZRH.js:16702:15)
    at insertOrAppendPlacementNodeIntoContainer (http://192.168.1.17:3000/build/entry.client-K3EESZRH.js:16702:15)
    at insertOrAppendPlacementNodeIntoContainer (http://192.168.1.17:3000/build/entry.client-K3EESZRH.js:16702:15)
    at insertOrAppendPlacementNodeIntoContainer (http://192.168.1.17:3000/build/entry.client-K3EESZRH.js:16702:15)
    at insertOrAppendPlacementNodeIntoContainer (http://192.168.1.17:3000/build/entry.client-K3EESZRH.js:16702:15)
    at insertOrAppendPlacementNodeIntoContainer (http://192.168.1.17:3000/build/entry.client-K3EESZRH.js:16702:15)
    at insertOrAppendPlacementNodeIntoContainer (http://192.168.1.17:3000/build/entry.client-K3EESZRH.js:16702:15)```

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 8
  • Comments: 31 (11 by maintainers)

Commits related to this issue

Most upvoted comments

This issue still happens with react 18.2.0 as I init the project with the indie stack.

Any update here?

@rphlmr I think this issue should be reopened. I deployed my first production remix app and have gotten consistent reports from users who have any extension that mutated the DOM (many many of them) I’m going to revert to react 17 tomorrow, but this issue should be considered ‘breaking’.

If you’d prefer, I can open another issue.

Something that I’ve been wondering about:

hydrateRoot(document, <RemixBrowser />);

I don’t think it’s a good idea to hydrate the entire document because for chrome extensions that modify the DOM (e.g. Apollo Client DevTools, LastPass, Dark Reader), they will add / change the DOM significantly by injecting CSS / JS.

In my case Apollo Client Devtools was causing the app to crash after upgrade to React v18, because it was injecting a script tag on client side.

Who have this problem could try running in incognito mode in order to exclude browser extensions.

I have the same problem with cypress ended up doing this workaround

// Dependencies
import { RemixBrowser } from "@remix-run/react";
import { hydrateRoot } from "react-dom/client";

if (process.env.NODE_ENV === "test") {
  require("react-dom").hydrate(<RemixBrowser />, document);
} else {
  hydrateRoot(document, <RemixBrowser />);
}

“Dark Reader” extension also triggers this error. Of course, disabling extensions isn’t a solution. I can’t ask visitors to disable all extensions to view my site. @MichaelDeBoey could you reopen this issue?

I just went back to using hydrate from react-dom instead of the new hydrateRoot, the fix suggested by @danestves for cypress, only works if you are running cypress locally.

For running it against deployed staging environments etc, it will not work.

Awaiting a fix in the meantime

@rphlmr Could we re-open this? Multiple people have faced this issue in the discord. I think the problem lies in upgrading an existing app but I have no idea why that would cause problems. I am fairly confident caching isn’t the issue. The problem persists across multiple browsers and private browsing .

I have a Remix stack following indie stack and my cypress runs fine (when it’s not timed out 🤭) on GitHub action.

I just have to add this, somewhere in cypress/support :

(source)

Cypress.on("uncaught:exception", (err) => {
  // Cypress and React Hydrating the document don't get along
  // for some unknown reason. Hopefully, we figure out why eventually
  // so we can remove this.
  if (
    /hydrat/i.test(err.message) ||
    /Minified React error #418/.test(err.message) ||
    /Minified React error #423/.test(err.message)
  ) {
    return false;
  }
});

But, even with entry files like the indie stack, you’ll still have an error in the console in prod (not breaking things but disgracious)

@rphlmr figured it out! remix-themes seemed to be playing weirdly with the new hydration in some edge cases. (I think the useTheme hook was returning conflicted values on the client and the server). I’ll open up a issue over there to track. Thank you for your help!

@rphlmr I just tried rm -rf tmp .cache build && npm run dev and the problem persists. Any ideas?

Also I am not using cypress, so the problem must be related but different

This issue remains.

import { hydrate } from 'react-dom'

Is working for now but not ideal since it only allows you to make use of react v17 features.

Happened to me due to Yoroi And agree with @ostwilkens expecting user (and developers) to disable browser extensions is not a solution

@hrgui yea, though I love that Remix gives us the full control, maybe hydrating document.head & document.body instead might help? Well Yoroi is injecting before <head> not sure what others do, if they inject into <head> oder <body> same issue would happen I guess? 🤔

@DAlperin Yes I can 😉 I reproduce if I npm run dev after a cypress run. But if i clean build folder and re run dev, no more problems 🤷‍♂️