remix-run/remix

hydration error when there is a chrome extension that modifies DOM

jin2255 opened this issue Β· 67 comments

What version of Remix are you using?

latest

Steps to Reproduce

I installed an extension that modifies DOM such as Grammar Checker into my browser and tried to develop a web app with Remix.
I get the following errors and the styles of the app were messed up.

Hydration failed because the initial UI does not match what was rendered on the server.
There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.

Expected Behavior

I have errors when I install any extension that modifies DOM into my browser

Actual Behavior

The favicon and styles are messed up
The important thing is that this error has already been mentioned many times and there is no exact solution
What's even more interesting is that the https://remix.run site doesn't have this error occur even if it was developed with Remix

This is a known gotcha, however, I agree it would be helpful if Remix could do something to prevent this from happening. For me the error only occurs in development. Has anyone found a workaround other than incognito or uninstalling the extensions?

We talked about this with @mjackson

The root of the problem is that remix hydrates the whole document, instead of a div on the body.

Not sure if the team is willing to reconsider this choice, and the consequences it would have on handling things like meta & so on. Maybe a good topic for today's live roadmap stream.

Thank you all for the answers.
It's a pity that there is no exact solution yet.
A few months ago, I started developing sites with Remix instead of Next and now I can't deploy to production because of this problem.
Hopefully, the team at Remix will fix this soon.
And I wonder how this error doesn't happen in the https://remix.run site.
Doesn't this mean that we already have the possibility to solve this problem?
If anyone knows, please... πŸ™

@jin2255 Are you experiencing this error when running in production? I only experience it in development.

This happens in both production and development.
The problem is not an error in the dev tools console.
The problem is that favicon and styles are disappeared.

We talked about this with @mjackson

The root of the problem is that remix hydrates the whole document, instead of a div on the body.

Not sure if the team is willing to reconsider this choice, and the consequences it would have on handling things like meta & so on. Maybe a good topic for today's live roadmap stream.

thank you, @machour
So, do we have to wait for the team to fix this?
πŸ˜₯

@jin2255 someone needs to post a proposal for change and a discussion needs to happen, in order to get this on the team radar:
https://remix.run/docs/en/v1/pages/contributing#feature-development

This happens in both production and development. The problem is not an error in the dev tools console. The problem is that favicon and styles are disappeared.

for me its only happening in production :'(

thank you @machour
I posted a discussion to #4902
I hope I can get a exact solution...

I am not getting any error in Firefox and incognito of chrome. Do anyone getting any error in production???

I face the same issue with https://chrome.google.com/webstore/detail/autoscroll/occjjkgifpmdgodlplnacmkejpdionan/related extension on development and production.

I was able to resolve my issue by invoking clearBrowserExtensionInjectionsBeforeHydration() prior to executing hydrateRoot().
https://gist.github.com/OnurGvnc/31f03f0d5237b78224aa083493fda645

mhmm, so it seems there is not exact solution yet
I will not use remix till the remix team solve the problem correctly or till I can sure it's possible to prevent this error correctly
my next project will be developed by next js or any other frameworks, no remix

Really I loved remix
but the remix community is not big yet and it seems remix has some potential errors
πŸ˜ͺ 😭
I hope I can use remix for my future projects later

thank you @machour , @OnurGvnc

Well @jin2255 I just needed to downgrade react version ( from 18.x.x to 17.x.x ) and every it is fine by now in our production environment.

@jin2255 Ryan and Michael discussed this in the Roadmap live stream last week.
I recommend to view these two parts:

So basically my understanding is:

  • Remix sees this as a React issue
  • That issue doesn't affect your website. It just means that React swaps your UI at some point, and user still don't notice it.
  • If you are able to show that this is actually a problem, and your website is breaking because of this, please set up a repository reproducing the issue, and the team will give this a deeper look.

Well @jin2255 I just needed to downgrade react version ( from 18.x.x to 17.x.x ) and every it is fine by now in our production environment.

When using CSS-in-JS, the styles fail to render, so as you can imagine it feels like the entire site breaks.

In summary, it's a React 18 problem and it can be seen at entry.client.tsx.

FAILS

hydrateRoot(document, <RemixBrowser />)

WORKS

hydrate(<RemixBrowser />, document)
// Warning: ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot instead.
// Until you switch to the new API, your app will behave as if it's running React 17

Reproduction

https://stackblitz.com/edit/node-czrvzk?file=app/entry.client.tsx

I used styled-components and I have a large project using styletron that also has this same issue and workaround.

This is not just a Chrome Extension problem.

If you share your website on social media, it opens in an in-app browser. Meta, at least, injects fancy stuff in the DOM (weird, right?), which leads to the same issue.

This one is more concerning to me, as the brand's reputation is at stake (they are linking the customers to an apparently broken site), and affects 100% of the users.

kzsa commented

On remix.run
If you scroll down to "Remix has a cheat code: Nested Routes." when viewing the home page with google chrome's built-in translator, the page runs into an error.

KΓ©pernyΕ‘kΓ©p – 2023-01-21 07-57-59

kzsa commented

This is not just a Chrome Extension problem.

If you share your website on social media, it opens in an in-app browser. Meta, at least, injects fancy stuff in the DOM (weird, right?), which leads to the same issue.

This one is more concerning to me, as the brand's reputation is at stake (they are linking the customers to an apparently broken site), and affects 100% of the users.

For the time being, I think it's better to avoid R18.
If you can't use the built-in Chrome translator on your site, there is a hidden accessibility problem. Content translation can be expensive and time-consuming process. Not all startups can finance content translation at the beginning.

Xiphe commented

Still with react@17 additional injected elements (such as the <style /> from styled-components) are wiped when hydrating - even when there are no hydration errors displayed

...unless they're placed as the last child of an element, which is not controllable in every case...

kzsa commented

Still with react@17 additional injected elements (such as the <style /> from styled-components) are wiped when hydrating - even when there are no hydration errors displayed

...unless they're placed as the last child of an element, which is not controllable in every case...

Right, I just checked that it is also 17.
image

thank you, all
I know this error doesn't happen in React 17.
I've seen many articles and advertisements saying that remix is much better than next.
So, I recently tried to use remix to businesses and found an unexpected bug, and the remix team just said it isn't a remix problem...
Disappointment!
I will go with React 18, Next JS

kzsa commented

Each tool has its own advantages, limitations and surprises. This applies to Next as well. (e.g. image optimization...) It is always difficult to choose. In my current project I have to avoid both Remix and Next. It doesn't mean that they are bad, it just means that I have to choose another one to achieve my current goals. I hope that this problem will be solved and we'll once again be able to choose from a wide range of tools.

Each tool has its own advantages, limitations and surprises. This applies to Next as well. (e.g. image optimization...) It is always difficult to choose. In my current project I have to avoid both Remix and Next. It doesn't mean that they are bad, it just means that I have to choose another one to achieve my current goals. I hope that this problem will be solved and we'll once again be able to choose from a wide range of tools.

Mostly use React for the frontend.
That's why I'm so obsessed with remix and next.
if I start with other frameworks now, it takes more time to make production.
which framework or stack do you prefer?

kzsa commented

I see that there will be progress on this issue. Someone is working on it. πŸ‘
https://remix.run/docs/en/v1/route/error-boundary

Remix.mp4
Xiphe commented

@kiliman figured out a workaround by not having remix render the complete document but "classic" style a <div id="root"> + stuff in <head> as side-effect: #5244 (comment)

kzsa commented

Each tool has its own advantages, limitations and surprises. This applies to Next as well. (e.g. image optimization...) It is always difficult to choose. In my current project I have to avoid both Remix and Next. It doesn't mean that they are bad, it just means that I have to choose another one to achieve my current goals. I hope that this problem will be solved and we'll once again be able to choose from a wide range of tools.

Mostly use React for the frontend. That's why I'm so obsessed with remix and next. if I start with other frameworks now, it takes more time to make production. which framework or stack do you prefer?

Stack depends on the task.
Learning a framework takes time. I rarely use frameworks. I can't say that sometimes I'm not seduced, but I prefer to rely on proven libraries. I have favourite tools to speed up progress, but it's a matter of taste.
For web development:
React, VanillaJS, Preact, just wanted to try Remix,
CSS, SCSS (Without fancy frameworks.)
node, Express, PHP, Laravel, Symfony, KeystonseJS
mySQL, SQLite, Mongo, GraphQL
Webpack, Parcel, Gatsby, Vite
I am now learning from the big guys about Web Components.
Linux, Vagrant, podman, QEMU/KVM

I have great respect for free software developers who contribute their knowledge to make my job easier. Building and maintaining a library/framework/tool takes a lot of effort.

I hope you find the right one for you,
Br: Zsolt

I'm experiencing this issue on Remix.run 1.11.1, and React 18.0.2.

Tried using hydrate, but same issue.

Issues appear as soon as I activate a plugin like Dark Mode. Incognito Mode or disabling extensions is not an option. I can't tell this to our thousands of users.

Found a small example online: https://stackblitz.com/edit/node-czrvzk?file=app%2Fentry.server.tsx, with this extension: https://chrome.google.com/webstore/detail/dark-mode/dmghijelimhndkbmpgbldicpogfkceaj

Xiphe commented

@designbyadrian the only solution I'm currently aware of is either downgrade to react@17 or to not render remix into the document but a <div id="root"> - see https://github.com/Xiphe/remix-island for how to do the latter.

@Xiphe I think this actually solved my issue! I'm using a custom Document component, but the fix is the same.

I really appreciate the examples given in the repo!

Edit: this fix causes new issues. I've written a ticket in the corresponding repo.

@sergiocarneiro Looks like its partially fixed in React 18.3.0-next-fccf3a9fb-20230213
React 18.2.0 https://stackblitz.com/edit/node-czrvzk?file=app%2Fentry.client.tsx
React 18.3.0-next-fccf3a9fb-20230213: https://stackblitz.com/edit/node-21xww3?file=app%2Fentry.client.tsx
Hydration errors still happen, but styles don't break
Need to check other cases too.

tmcw commented

Right to assume that the current state of this issue is that we're waiting for React 18.3.0 to be released, and until then most Remix sites for most users will have a flash of unstyled content?

That issue doesn't affect your website. It just means that React swaps your UI at some point, and user still don't notice it.

@machour the user will see a flash of unstyled content.

@tmcw You can try the remix-island package .

mh4ck commented

It's not only a extension problem, a lot of scripts modify the body like adsense etc. if remix want to survive it needs to solve it. I think it's not a big thing to just render the head separately and the content in a div. There are already examples on the web how to modify the entry.server.jsx and entry.client.jsx for that. But it needs to be out of the box. It's a BIG PROBLEM right now!

Of course it currently just write an error into the logs and replace the content to client side rendering, but had anyone see the CLS score while doing it? It's a 100% layout shit while doing so! You can kick SEO Optimizing down the stairs if that stay like it is.

@mh4ck try the react canary version, it should be fixed.

Tried react version 18.3.0-canary-8ec962d82-20230623 but the problem persists. Unfortunately I can't make a repro (due to the size of the project) but this issue is happening in production and it's causing some styles to break and the user gets a half-styled page. Quite a problem UX wise.

We've had a number of these types of issues filed - and we're pretty confident they boil down to a React issue which we believe is fixed in 18.3. I'm going to close out the rest of the issues and choose this one as the canonical since it's been around for a while and seems to have the most traction.

Other related issues:

Partial React fix in 18.2: facebook/react#22833
React tracking issue: facebook/react#24430

We'll keep this open until 18.3 is released to confirm it resolves the primary issue.

I just tried with 18.3.0-canary-1a001dac6-20230812 and unfortunately I'm still getting the "Hydration failed because the initial UI does not match..." error when running in Cypress (even locally).

I have also tried with 18.3.0 canary and despite I still having this hydration errors Im not having the flashing screen errors anymore

upgrading to 18.3.0-canary-ddff50469-20230829 solved the same issue I have with React-Select which uses Emotion.

I fixed with this solution.

See the repo and the demo:
https://github.com/franklinjavier/remix-fixed-hydration-issue
https://remix-fixed-hydration-issue.vercel.app

export default function App() {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta content="width=device-width, initial-scale=1" name="viewport" />
        <Meta />
        <Links />
      </head>
      <body>
        {/**
         * This removes anything added to html from extensions, causing hydration issue
          https://github.com/remix-run/remix/issues/4822
        */}
        <script
          dangerouslySetInnerHTML={{
            __html: `document.querySelectorAll("html > script").forEach((s) => s.parentNode?.removeChild(s));`,
          }}
        />
        <Outlet />
        <ScrollRestoration />
        <Scripts />
        <LiveReload />
      </body>
    </html>
  )
}

On a fresh install with npx create-remix@latest --template remix-run/remix/templates/express
Hydrations errors continue to occur even after applying this:

<script dangerouslySetInnerHTML={{ __html: `document.querySelectorAll("html > script").forEach((s) => s.parentNode?.removeChild(s));`, }} />

Where did you put this snippet?

app/root.tsx

here's mine:

export default function App() {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
      <script
          dangerouslySetInnerHTML={{
              __html: `document.querySelectorAll("html > script").forEach((s) => s.parentNode?.removeChild(s));`,
          }}
      />
        <Outlet />
        <ScrollRestoration />
        <Scripts />
        <LiveReload />
      </body>
    </html>
  );
}

Let me try with express template

It worked here
image

My issue was fixed by updating to the latest Chrome: 118.0.5993.117
Sadly, didn't record the breaking version

Hmm, so this workaround isn't 100% effective, I need to think of another alternative

The best solution I found was to upgrade React to it's latest canary version 18.3.x. maybe it helps to you

Upgrade to canary worked for me !

HuyAms commented

I'm on React 18.2.0 and face up with the same issue. Any updates?

I'm on React 18.2.0 and face up with the same issue. Any updates?

Go for canary that's the only way

Upgrade react AND react-dom to latest canary version

Upgrade react AND react-dom to latest canary version

Not sure we can do this in the guise of shopify-remix-app-template and the dependency tree

Upgrade react AND react-dom to latest canary version

Not sure we can do this in the guise of shopify-remix-app-template and the dependency tree

Ikdemm commented

I'm on React 18.2.0 and face up with the same issue. Any updates?

Go for canary that's the only way

Is it safe to do this for a Remix project on PROD? πŸ€”

Per the React team, yes. Next.js (at least the app router) only uses canaries so any production Next.js app using the App Router is using a canary.

It's really up to you and your team though if you feel comfortable shipping a canary release to production.

I'm facing the same problem as described. When having Grammarly on, the favicon jumps in production and development, and a bunch of hydration errors appear in the console.

It's been over a year and is quite critical. We are talking about people developing entire websites and then seeing it all broken because HTML is injected dynamically, which is a common occurrence.

Is there a hack other than using the React canary version?

I'm facing the same problem as described. When having Grammarly on, the favicon jumps in production and development, and a bunch of hydration errors appear in the console.

It's been over a year and is quite critical. We are talking about people developing entire websites and then seeing it all broken because HTML is injected dynamically, which is a common occurrence.

Is there a hack other than using the React canary version?

Nah I don't think so ... https://twitter.com/BrooksLybrand/status/1763678388806394127?t=pxowwBKSwYI-VP4yADlDvw&s=19

my workaround to prevent any DOM modifications until after hydration happens
root:

        <script
          dangerouslySetInnerHTML={{
            __html: `
              const observerConfig = {
                childList: true,
                subtree: true,
                attributes: true,
                attributeOldValue: true,
                characterData: true,
                characterDataOldValue: true,
              };
              window.hydration_observer = new MutationObserver((mutations) => {
                mutations.forEach((mutation) => {
                  switch (mutation.type) {
                    case 'childList': {
                      window.hydration_observer.disconnect();
                      mutation.addedNodes.forEach((node) => {
                        try {
                        mutation.target.removeChild(node);
                        } catch (e) {
                          console.error(e);
                        }
                      });
                      window.hydration_observer.observe(document, observerConfig);
                      break;
                    }
                    case 'attributes': {
                      mutation.target.removeAttribute(mutation.attributeName);
                      break;
                    }
                  }
                });
                
              });
              window.addEventListener('DOMContentLoaded', () => {
                window.hydration_observer.observe(document, observerConfig);
              });
            `,
          }}
        />

client entry

  hydrateRoot(
    document,
    <Provider>
      </StrictMode>
    </Provider>,
  );
  window.hydration_observer.disconnect();

I just wanted to smoothly deploy to the production environment, I didn't expect so many pitfalls. If I had known it would be like this, I wouldn't have used this tool. This is my temporary solution, I just comment out this line, and the world becomes quiet.

/node_modules/react-dom/cjs/react-dom.development.js:

function throwOnHydrationMismatch(fiber) {
  //throw new Error('Hydration failed because the initial UI does not match what was ' + 'rendered on the server.');
}

@MaeThird, update to the React canary version to resolve the hydration issues. This is a React issue because Remix hydrates the entire document instead of a single div.

Also make sure you pin the version and add to overrides
https://twitter.com/kiliman/status/1769730243772780825

@kiliman works now! Thank you.

I'm facing this issue with react 18.3.1

v18.3 used to be the canary version that fixed this. It's now React 19. So install the latest RC react@rc react-dom@rc.

I was able to resolve my issue by invoking clearBrowserExtensionInjectionsBeforeHydration() prior to executing hydrateRoot(). https://gist.github.com/OnurGvnc/31f03f0d5237b78224aa083493fda645

This definitely helped, but Grammarly was still acting up and messing up the entire page. Until a stable version of React 19 is out, I found this workaround.

startTransition(() => {
  clearBrowserExtensionInjectionsBeforeHydration();

  const root = hydrateRoot(
    document,
    <StrictMode>
      <MuiProvider>
        <RemixBrowser />
      </MuiProvider>
    </StrictMode>,
    {
      onRecoverableError: () => {
        console.log("Hydration failed! Attempting recovery...");
        root.render(
          <StrictMode>
            <MuiProvider>
              <RemixBrowser />
            </MuiProvider>
          </StrictMode>,
        );
        console.info("Recovery successful!");
      },
    },
  );
});

Thanks @OnurGvnc for clearBrowserExtensionInjectionsBeforeHydration