Flicker on hydrate when using Parcel SSR
I'm getting a white flicker that happens when ReactDOM.hydrate is called. I've tried my best to follow the documentation but I can't figure out how to fix it.
I trimmed down my project to a simple repro which you can find here: https://github.com/thomasjm/ric-hydrate-flicker
Any help would be much appreciated :)
👍 will take a look
- you have a mistake in SSR code - double closed
div
- fullHtml = fullHtml.replace(`<div id="app">`, `\n<div id="app">${html}</div>`)
+ fullHtml = fullHtml.replace(`<div id="app">`, `\n<div id="app">${html}`)
- you have a mistake in client code - dynamically created component. It is just unique every render (that is the root cause)
+ const Simple = lazy(() => import("./pages/simple"));
....
- {renderPage(lazy(() => import("./pages/simple")))()}
+ {renderPage(Simple)()}
3. Please use `dev` mode to debug the build - hydrate will complain about markup mismatch
"create-bundle:client": "cross-env NODE_ENV=development BABEL_ENV=client parcel build app/index.html -d dist/client --no-source-maps --no-minify --public-url /dist/client",
4. You can also check how its working using manual debugging
```js
console.log('before', element.innerHTML);
ReactDOM.render(app, element);
console.log('after', element.innerHTML);
Thanks!!! So happy to find a fix.
One question though -- I made those changes (just pushed them) and I'm still seeing a warning:
Warning: Did not expect server HTML to contain a <h2> in <div>.
However, the before and after prints both show <h2>Simple simple simple</h2>, so it seems like nothing changed. Shouldn't this warning be gone now?
(Note: your point number 4 used ReactDOM.render but I assume you mean ReactDOM.hydrate so I used that.)
Well, look like using Suspense (LazyBoundary) is the root cause for this.
- if you change
lazytoimporter- everything will work as before - if you will after that remove
LazyBoundary, which is no longer required - the problem will be solved. 👉 So the problem is bound toSuspenseboundary.
I've downgraded your example back to 16.9.0, and the problem was resolved once again. So here is the root cause - https://github.com/facebook/react/issues/16938 - which was expected to be resolved a while ago, but look like it strikes back.
According to this test - https://github.com/facebook/react/pull/16945/files#diff-ab371863932cd2e8f0ba14ff2eaab380R687 all you have to do (and it will fix the problem, I've tested) - remove fallback prop from LazyBoundary (typescript will complain).
Citing https://github.com/facebook/react/pull/16945
So technically a workaround that works is actually setting the fallback to undefined on the server and during hydration. Then flip it on only after hydration.
Not very happy to discover this problem a year after it occurred (still on 16.9 on projects using Lazy). Tested 16.10.1 - and it's completely broken (eating dom nodes, not just recreating them). 😭
Just checked:
- you can render all boundaries with
fallback={undefined} - and update
fallbackto something defined inuseLayoutEffect(for example) - however is any
lazywould throw (for example components which were not server side rendered) that would throw an error. So it all depends on how "full" page is rendered, and if some pieces were not rendered - why and how.
Hmm, I tried switching to importedComponent and got it to hydrate without any warnings. I just pushed those commits.
Is there any reason to prefer the lazy API over the pre-lazy API? Is pre-lazy older or worse somehow? If importedComponent works properly then I'd rather use that than try to work around issues with LazyBoundary. But wondering what solution you'd recommend.
Lazy gives you a Suspense boundary, so you can "await" loading on a bit higher level, including using new useTransition hooks to properly await for loading before actually changing the page.
It all depends on the use case, but if you have more that one deferred component on the page - you might prefer lazy