We request the page, and initially it looks fine. It appears server side hydration is working as expected. But a second or so later, after React initialises on the client, some html ends up rendered in the wrong place (sometimes randomly inside sibling elements).
The page source html is correct, so it must be rendering okay on the server.
The react tree itself, and where it says the html is, is in line with what we would expect.
However the html itself is wrong, and out of sync with the react tree shown in the dev tools.
Forcing a re-render in the dev tools appears to fix it.
eg
React tree and page source show:
<div class="a"></div>
<div class="b"></div>
<div class="c"></div>
but actual rendered html is something like:
<div class="a">
<div class="b"></div>
</div>
<div class="c"></div>
The rendered html should match the react tree in the dev tools exactly.
Appears to replicate across all machines and browsers.
We've also seen this issue in other places in the app, this is just the most obvious one. A pattern we've noticed is that it only happens in elements rendered via an array map (ie the cards).
We've done all we can to optimise these lists (memoized selectors, unique element keys etc etc) and I really dont think what we're trying to do should be too much work for react.
It appears to happen when commons.js is loaded. Im not 100% what comes in that file, but i imagine it's the core react scripts that then initialise and create the virtual DOM, which is why the page rerenders after it loads?
We're using styled-components, but this issue was present before switching to styled components from sass.
You'll be able to see our redux actions in redux devtools.
I saw on another issue something similar was happening due to invalid html. I've run this (https://validator.w3.org/nu/?showsource=yes&doc=https%3A%2F%2Flounge.gamerjibe.com%2Flounge%2FMoxieBoosted%2Fmembers) and fixed the errors locally, but it doesn't fix the issue.
Any help here would be hugely appreciated. I can't seem to find anyone else having the same issue.
Hey, we're having the exact same issue you're describing in our app.
This only happens when we bump from 8.0.4
to 8.1.0
. Using 8.0.4
there's no such issue.
I also wasn't able to identify the root cause (or reproduce outside our app). It's always the same piece of the DOM which is broken after the hydration phase. In our case it's components inside a React.Fragment
who're mixed up (not an array directly.)
Intersting, I'm glad we aren't the only ones! We actually upgraded from v7 to v8 in attempt to fix the issue, so for us it was definitely present on older versions too.
Hi, unless someone can provide project source code that causes this to reproduce there's not much we can do to fix this.
This, however, sounds like a React bug and not a Next.js bug -- all DOM interactions are handled by React via ReactDOM.hydrate
. Does React print any warnings about your render not matching the DOM on hydration?
Please don't close this issue, as we're still not sure where the issue lies. It took me hours to find this discussion!
Has anyone got an update on this?
No update, I couldn't figure out the root cause yet... We're using the same version of React, but as soon as we bump to latest Next we run into this issue.
For now we've just pushed back on updating our Next version.
Thanks, I'll try downgrading. Will update this comment if I see a difference.
Have also noticed this only in production on an app, inconsistently. Downgrading to a version below 8.1.0 fixed for now.
Our solution for now has been to optimise our selectors and render cycles to make sure big lists are rendering as infrequently as possible.
The main thing here was to, instead of connecting and getting a list of objects from the store and passing them to their respective components, get ids from the store and pass them to the components letting the components connect themselves.
This made it easier to memoize the individual list items props and store connections, and meant the list as a whole rendered less frequently, with only the individual items rendering.
This seemed to fix it for us, so I imagine it's something to do with React/Nextjs list rendering and us asking too much of it, but I feel like it's still an issue the Next team should be aware of and look into
It's not a big list issue for us, it's mixing buttons next to each other; not even in an array, just in a fragment <></>
.
Looks like the conflict between SSR and client side rendering can happen pretty much anywhere.
Ahhh very interesting! So it might not even be directly related to big expensive renders? If you can properly memoize that component with the buttons so it doesnt rerender on hydration does it fix the issue?
I downgraded to 8.0.4 and no longer experience this issue.
so @Timer, if people have been able to fix by downgrading, can we accept that this COULD be a bug and reopen the issue?
Unfortunately we were seeing this bug back with next v7 (as mentioned previously by @mattwills8), and downgrading to 8.0.4 also does not solve it.
We'd love to be able to come up with a reliable repro for you @Timer but we haven't been able to. This only happens during an initial SSR render, we've never seen it during a client side render, even navigating to a different page and then back fixes it.
This issue should absolutely be reopened regardless to increase visibility for others running into it.
Here is a repro of what we're running into: https://github.com/timdavish/next-html-out-of-sync-repro
https://github.com/timdavish/next-html-out-of-sync-repro/blob/master/pages/index.js#L6
This is not a bug in your case. SSR + Hydration have to match, otherwise React does a full re-render throwing away the SSR'ed elements, hence why it warns.
Rendering based on if (!process.browser)
inside of render/functional components should be avoided, instead you can use useEffect
or componentDidMount
to know if the app was hydrated/ready.
This totally makes sense in regards to the warning, but it still seems odd that the end result is just completely mis-rendered html. If it needed to throw away all the SSR'ed elements during hydration (due to the mismatch) why doesn't the re-render then provide the expected result?
Regardless, using useEffect
with some state that keeps track of if the component is mounted solves our issue, much appreciated!
For anyone else who's curious, you can easily create a wrapper component to skip SSRing some content with something like the following:
import React, {Fragment, useEffect, useState} from 'react'
export const NoSSR = ({children, fallback = null}: NoSSRProps) => {
const [isMounted, setIsMounted] = useState(false)
useEffect(() => {
setIsMounted(true)
}, [])
return <Fragment>{isMounted ? children : fallback}</Fragment>
}
but it still seems odd that the end result is just completely mis-rendered html
Please file this bug with the React team if you wish to see the behavior changed.
For anyone else who's curious, you can easily create a wrapper component to skip SSRing some content with something like the following
This solution worked for me. Using next version 9.0.6
Thanks @timdavish
Hmm, I also had this problem on next version 9.3.2
. For me, I fixed by just avoiding the React.Fragment
entirely. This seemed to fix the inconsistent markup.
Hmm, I also had this problem on next version
9.3.2
. For me, I fixed by just avoiding theReact.Fragment
entirely. This seemed to fix the inconsistent markup.
A good article that describes the issue and fix with Tim's solution posted above:
https://joshwcomeau.com/react/the-perils-of-rehydration/
I basically had the same problem, I was conditionally rendering parts of my page and I had to wait for the component to be mounted so I implemented the hasMounted trick.
Basically, what you need to do is do whatever transformations that depend on any browser API just after the component has mounted:
import React from "react";
export default function useHasMounted() {
const [hasMounted, setHasMounted] = React.useState(false);
React.useEffect(() => {
setHasMounted(true);
}, []);
return hasMounted;
}
import { useEffect, useState } from "react";
import useHasMounted from "./useHasMounted";
export default function useResponsive() {
const intialValue = process.browser ? window.innerWidth < 769 : false;
const [isMobile, setIsMobile] = useState(intialValue);
const hasMounted = useHasMounted();
useEffect(() => {
const handleResize = () => {
if (window.innerWidth > 768) {
setIsMobile(false);
return;
}
setIsMobile(true);
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return isMobile && hasMounted;
}
When React do the rehydration phase, if the output rendered in the server side is different from the generated in the rehydration phase this makes React be confused and makes him render the output in a wrong way, so what you need to do is assure that the output generated by the server be exactly the same to the generated by the rehydration phase (that have access to browser apis, this is the reason why the output differs) and wait for the component to mount, that happens after the rehydration phase to make any changes based in-browser API or any other type of client-side data
Most helpful comment
Please don't close this issue, as we're still not sure where the issue lies. It took me hours to find this discussion!
Has anyone got an update on this?