Open yongdamsh opened 2 years ago
@scerelli I tried to see if this extenion's source code is available but wasn't able to find it on github. Do you know if it is reviewable?
Also, when the hydration fails is the entire app broken or does the app recover and there is just an error in the console?
Can you link a repro?
@gnoff here you go: https://github.com/scerelli/hydration-issue-reproduction
Simply install the Dashlane extension and create a free account. However, this issue may also occur with any other browser extension that modifies the DOM.
I have observed that in my situation, the problem occurs when I am using Yup with React Hook Form. However, I am uncertain about the cause of the problem since the component that is displaying the issue is also a client component. Interestingly, on a Next 12/React 17 setup, the same code does not trigger the error.
I'll be following this issue closely. I was attempting to create an app using remix.run and couldn't even get an initial basic build working because of this issue and I run a lot of browser extensions.
Just me thinking out loud here, but it's too bad the browsers can't enforce some sort of standard for extensions for how they get to manipulate the head and/or body content. Seems to me if there was that set standard, it would be a lot easier to React to look for and account for those changes.
@scerelli thanks for the repro. I was also hopign to see the dashlane source but I can tell they are injecting dom nodes and adding attributes deep within the application.
React was not updated to be more tolerant of deep mismatches and instead focussed on handling cases in the head and body more effectively. For a lot of 3rd party stuff this is sufficient because that's all they do. But whenever an extension modifies the HTML deeply in the document React can't tell whether that was because you rendered different stuff on server/client or whether it was intentional.
The reason this was not a probem in React 17 is that version just does an insert and patches up the DOM rather than falling back to client rendering.
The fact that you get a bunch of big red errors is offputting and we should figure out a way to surface meaningful info for devs in dev mode to figure out if their app is not correctly hydrating in the baseline case but not cause undo worry when there are incidental hydration errors which are always going to happen especially for users that use 3rd parties that modify the DOM.
The good news is that your repro demonstrates that React can now effectively recover from a hydration mismatch even when the entire document is rendered by it. Before this would have wrecked the whole app because the html element would be unmounted in an ineffective way and it would fail to remount correctly.
I'll continue to gather info from these reports and may someday pursue a more ambitous look at coexisting with extensions deep in the DOM but I don't have anything planned for that at the moment. Thanks for taking the time to report this
@gnoff Thanks to you for taking the time to explain further what's going on. Great work!
Hello @gnoff @gaearon ,
I am a developer of a dev tool chrome extension (Tailscan) and thus far, three of my users have reported issues in combination with Remix / React 18. I've been following this issue for a while now and want to chip in, just to give an extra insight into the issue. I don't use React / Remix myself and so I can't easily replicate things, but if it's useful, I can give you access to my private repository containing the chrome extension's code and a packaged version, just in case you want or need to dive in deeper. In any case, I very much appreciate the extensive information both of you have provided, thank you!
All three users that have ran into the issue, receive the same errors as others have described above. It's either hydration errors or did not expect server HTML to contain ...
errors.
One user reported the following error:
Warning: Did not expect server HTML to contain a <tailscan-container> in <html>.
Another user wrote this:
Changing the browser extension settings to only load the extension when clicking on the extension icon, instead of automatically on page load, fixes the problem on first page load. If I refresh the page a lot of times I can be lucky that the extension loads after the page is loaded (the code for the service worker pops up in the source tab in the dev tools a little delayed when this happens). My react version is set to "^18.2.0"
Furthermore, that same user reports:
I can consistently make the extension work by doing the following:
- Set site access to "On click" (referring to the aforementioned setting. In Chrome, right click the extension icon > "this can read and change site data" > "when you click the extension")
- Access the site in a newly open tab If I refresh the tab (not opening a new tab) it gets the hydration error where Tailscan won't load.
- If I open a new tab again Tailscan will work without any problems even though I navigate around, refresh the page etc. It is only after Tailscan has been initialized/loaded the first time, it breaks on subsequent refreshes.
The chrome extension does a couple of things within the DOM:
<body>
element in the <html>
element.data-tailscan-id
and toggled-off-classes
)<style>
elements inserted at the bottom of the <head>
elementThe way this chrome extension works is very similar to other extensions such as password managers inserting things in <input>
elements, other devtool extensions, etc.
I think the explanation of the users match up with the explanation @gaearon gave. Furthermore, the changes in the next stable that @gnoff mentions, will help solve some the issues I have. Two unknown for me are still:
And if either of them does cause problems, whether there is any way to go about it that doesn't break the app.
åI want to know the result of this problem.I have tried many ways, but I still can't solve the problem.
Hey @Mordred thanks a lot for this quick hack, I modified it slightly to also get rid of
input
s withinhtml
as the Yoroi extensions injects a hidden input directly into the<html/>
(not<body/>
)document.querySelectorAll("html > script, html > input").forEach((s) => { s.parentNode?.removeChild(s); });
Using this now in my Remix Deno Stack with Streaming 🥳
What worked for me was:
[...document.querySelectorAll("html>*")]
.filter((elem) => !elem.matches("head,body"))
.forEach((s) => {
s.parentNode?.removeChild(s);
});
This removes anything added to html from extensions like grammarly. Also add suppressHydrationWarning={true}
in the html
and body
tags in the case of grammarly. I faced this situation in production in an app created with npx create-rsc-app@latest my-app --ssr
(it's a setup with RSC and SSR I've made myself).
Not every script tags and style tags comes from browser extensions. Kaspersky Internet Security -> Anti-Banner feature is injecting HTML tags on the network layer (with MiTM, but that is not relevant now :D). This effectively triggers hydration errors.
For now: I'm extending my querySelectorAll
to
document
.querySelectorAll(
'html > *:not(body, head), script[src*="extension://"], link[href*="extension://"], script[src*="scr.kaspersky-labs.com"], link[href*="scr.kaspersky-labs.com"]',
)
.forEach((s) => {
s.parentNode.removeChild(s);
});
If this will not work I swear I will add data-my-tag
to every script
, link
, and style
tag which were generated by my server 😂
There is also another problem. React is trying to recover but it replace all HTML elements with new ones. This breaks CSS-in-JS solutions e.g. Emotion, because they already have references to old elements where they are still injecting their styles.
I'm using the MUI library in one of my projects and after the hydration error, all styles are gone.
There is an onRecoverableError
but it is called after the DOM was changed by React.
Can we have a callback that will be called before React tries to recover from the hydration mismatch? (@gnoff @gaearon)
I would like to make a snapshot of DOM for better debugging of what people have injected in their browsers. Also Emotion (or another) library can be reset (clear their elements refs) before react switch to client-side rendering (An error occurred during hydration. The server HTML was replaced with client content in <#document>.
).
Hey everyone 👋🏽
I am currently facing the same issue on my project. I have created the following reproduction repo , in my case I use Remix + Vite + Tailwind.
But the hydration issues have been around long before the Vite integration. It was only with the introduction of Vite, that this was most noticeable because, on Save, and after the HMR kicks in you will see the styles being removed from the DOM.
The following solves my issue:
react
and react-dom
following versions: "react": "^18.3.0-next-fccf3a9fb-20230213",
"react-dom": "^18.3.0-next-fccf3a9fb-20230213",
And this fixes my styling issues despite continuing to get hydration erros in the console.
Knowing now what I know, the root cause might be react
related but I still left an issue at vite's
repo and I am wondering if remix
staff can do something about this in the meantime.
my hydration error got fixed with:
"react": "^18.3.0-canary-3d9b20132-20240124",
"react-dom": "^18.3.0-canary-3d9b20132-20240124",
For testing i added back the cloudflare email obfuscated script injection + enable back the chrome extensions that triggers the hydration issue for me, Apollo client dev tools / Requestly and have not seen the issue 🙌
I cant believe this is still a problem for the past 2 years. I have LastPass installed, and even a Hello World app with ONE input tag type "text" and a placeholder "email" is causing the hydration issue. and no, suppressHydrationWarning does not fix the problem. (placed it everywhere without luck) :(
I'm getting the same error and I'm looking for a solution.
injected elements with style attribute throw an ssr mismatch error... why is this not fixed yet...
With all due respect @Omar-Abdul-Azeez, but it's an OS project..
Can confirm LastPass browser extension causing the hydration mismatch.
Can confirm LastPass browser extension causing the hydration mismatch.
Can you please tell me if this happens on 18.3.1 and on canary?
Seems react 19 (beta) might fix this, for me at least. Renders nicely with browser addons without errors
Can confirm LastPass browser extension causing the hydration mismatch.
Can confirm the same. Lastpass browser extension was causing error.
Lastpass issue confirmed here as well. Took me a while to figure it out and ended up here..
Seems react 19 (beta) might fix this, for me at least. Renders nicely with browser addons without errors
Tried this, unfortunately didn't work for me. Confirmed still an issue with LastPass browser extension causing it.
Still seems to be an issue when testing with Remix and React 18.3.1. It happens when the browser tab has been inactive for some time and it is focused again.
same happening with extension "I still don't care about cookies".
Can confirm that all our issues introduced by upgrading to React 18 were solved by upgrading to React 19 🎉️ (currently "react": "^19.0.0-rc-0bc30748-20241028"
). Both stuff injected by extensions into <html>
or <head>
and altered dom deep nested in the app.
I still get the warning with LastPass on the Oct 29 2024 React 19 and React-Dom 19 versions 19.0.0-rc-603e6108-20241029...but I love the new messaging. It shows git-style "minus" signs to show the difference between the server and client renderings, not to mention way better explanations and formatting. I think it is worth showing a screenshot for those who don't have this level of detail in React 18. Kudos to developers to analyze the issue to this level of detail.
UPDATE: I submitted this issue in the LastPass community. Not that it will go anywhere...perhaps React can still do something about working with plugins (?)
if lastpass adds the tag suppressHydrationWarning I believe that the error could go away.
At least that's how jam chrome extension works
React version: 18.0.0, 18.1.0-next-fc47cb1b6-20220404 (latest version in codesandbox)
Steps To Reproduce
UI mismatch error occurs at hydration time
Link to code example: https://codesandbox.io/s/kind-sammet-j56ro?file=/src/App.js
The current behavior
If a script tag is inserted before the head tag due to the user's browser environment such as a plugin, it is judged as a hydration mismatch and the screen is broken.
https://user-images.githubusercontent.com/4126644/164953071-14546c74-d9ab-4a6f-8f99-6712f29c6dd6.mov
The expected behavior
This problem may be a part that each third party needs to solve, but I'm wondering if it's possible to handle an exception in the hydration matching logic of React.