Flash of unstyled Content when running via build and serve
Hi,
First, thanks very mich @rohit-gohri for the excellent tool! Really helpful!!
Got a weird FOUC (flash of unstyled content) issue - we're using redocusaurus in a locally built instance.
I noticed that on the deployed site (https://redocusaurus.vercel.app/examples/) there's no problem, BUT if if I download the code and run the example locally:
yarn build && yarn serve
Then go to an API page, and refresh the page, it does something like this for a second, then the site resolves itself (I'm guessing due to late loading of css?). This is identical to what I'm seeing in my instance.

Another note here is if I run the app in 'dev mode' via yarn start there's no issue.
(btw, if there's ideas on where to look but it's a bit complicated, I'm happy to help with a PR)
Any ideas?
Update: I noticed this only occurs in dark mode, and it does happen on the vercel site https://redocusaurus.vercel.app/examples/ when you switch, so it must be related to the theme. I'll check this out further
Possibly related. After building my site (npm run build) and then serving the content (npx serve), the search icon fills the page until the page has loaded:

I've tested this with:
- A fresh install of docusaurus & redocusaurus
- https://redocly.github.io/redoc/openapi.yaml
Note that you won't experience the bug when running npm start; only when serving the built content.
p.s. Thank you for your work @rohit-gohri 🙇♂️
Yes @mfaux this is exactly what I saw as well. Interestingly for me, light mode was not as problematic on this as dark mode. For the time being, I'm deployed using npm start and while it's not ideal, it works ok.
Also in his vercel deployment, the problem only appears clearly in the dark mode setting, not light mode. Sorry been a bit busy on other things to get time to get back to this, but I'd welcome any ideas.
The problem is definitely with the ServerStyles and hydration. I suspect when react hydrates it removes the server style and then computes fresh styles in js. If we can somehow persist the server styles (maybe remove them after react has hydrated?) then I think it would solve this issue
@rohit-gohri thanks for the insight - does it use a completely separate model than the base docusaurus docs build, which doesn't have this issue?
We render the styles component only on server - https://github.com/rohit-gohri/redocusaurus/blob/main/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerStyles.tsx by modifying the webpack config only if it's a server build here - https://github.com/rohit-gohri/redocusaurus/blob/df43d742c376780d25a78e19731d43f3491b4ede/packages/docusaurus-theme-redoc/src/index.ts#L31-L38
On client it renders an empty div - https://github.com/rohit-gohri/redocusaurus/blob/main/packages/docusaurus-theme-redoc/src/theme/Redoc/Styles.tsx
What I was trying to use do was use this approach - https://github.com/facebook/react/issues/10923#issuecomment-338715787 and skip hydration for the styles component altogether but couldn't get it to work
Got it - thanks a lot for the insight. Will definitely be interested to investigate further when I have a free moment.
Turns out that is not the problem. Fixed the hyrdation issue in https://github.com/rohit-gohri/redocusaurus/pull/223 but this issue is still there because all the class names change when client code loads
That can be fixed by adding babel-plugin-styled-components to the user's babel.config.js file.
Can you try the release at https://redocusaurus-dcul1rve3-rohit-gohri.vercel.app/ to see if you can reproduce the issue?
And if not then you can try this test release in your project to see if it fixes the issue
yarn add [email protected] babel-plugin-styled-components@^2
And then adding babel-plugin-styled-components to babel.config
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
plugins: ['babel-plugin-styled-components']
};
HI @rohit-gohri problem still exists - I'm happy to do a screen share if you'd like to discuss further. The problem on that link still does not happen in light mode, only in dark mode.
I assume you're not able to reproduce this?
I was using Firefox, there I could not reproduce. Checked in Chrome now, reproducible very easily. But I think the changes are in the right direction. Will work on it later now.
Awesome, thanks again for digging in! Strangely, I just tried on Firefox (on latest MacOS, v102.0.1), and I see the problem there as well (on your Vercel link)
hey @rohit-gohri, any progress on this issue?
Work around create a plugin and add a custom loader
module.exports = function (context, options) {
return {
name: 'redocusaurus-preload',
injectHtmlTags() {
return {
preBodyTags: [
`
<script>
let isPath = [the redocusorus paths].includes(location.pathname)
if ((window.performance && performance.navigation.type == performance.navigation.TYPE_RELOAD || window.performance && performance.navigation.type == performance.navigation.TYPE_NAVIGATE) && isPath) {
showRedocFrag(false);
}
function showRedocFrag(toggle){
const range = document.createRange()
const displayContent = toggle?'block':'none';
const displayLoader = !toggle?'block':'none';
const frag = range.createContextualFragment(\`
<style>
.redocusaurus{
display: \${displayContent};
}
.redocusaurus-styles,
.redocusaurus-styles:after {
border-radius: 50%;
margin-top: 30vh !important;
margin-bottom: 50vh !important;
width: 10em;
height: 10em;
}
.redocusaurus-styles {
display: \${displayLoader};
margin: 60px auto;
font-size: 10px;
position: relative;
text-indent: -9999em;
border-top: 1.1em solid rgba(255, 255, 255, 0.2);
border-right: 1.1em solid rgba(255, 255, 255, 0.2);
border-bottom: 1.1em solid rgba(255, 255, 255, 0.2);
border-left: 1.1em solid #ffffff;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
-webkit-animation: load8 1.1s infinite linear;
animation: load8 1.1s infinite linear;
}
@-webkit-keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
</style>
\`
)
document.querySelector("head").append(frag)
}
</script>
`
],
postBodyTags: [
`
<script>
let hasPath = [the redocusorus paths].includes(location.pathname)
if ((window.performance && performance.navigation.type == performance.navigation.TYPE_RELOAD || window.performance && performance.navigation.type == performance.navigation.TYPE_NAVIGATE) && hasPath) {
let redocusaurusDoc = document.querySelector('.redocusaurus')
document.onreadystatechange = function() {
if (document.readyState === "complete") {
showRedocFrag(true);
}
}
}
</script>
`
],
};
},
};
};
@Snivio Thank you that's a great workaround! (tip for implementers, if your whole site is docusaurus you can get rid of those hasPath/isPath variables)
For me just added plugins: ["./redocusaurusFlashWorkaroundPlugin.js"] and voila!
@Snivio It'll be great if you can open a PR adding that plugin to redocusaurus. I think it can go directly to the theme - https://github.com/rohit-gohri/redocusaurus/blob/main/packages/docusaurus-theme-redoc/src/index.ts
We can put it behind an option, it's a great workaround until this gets fixed
A colleague came up with the following work around.
If you're using @docusaurus/preset-classic , add this to /src/css/custom.css:
/**
* Prevent redocusaurus from displaying html before its styles are fully
* loaded. This prevents large icons from being displayed before they
* properly styled.
*/
.redocusaurus {
display: none;
}
body[data-rh] .redocusaurus {
display: block;
}
I got into this topic a little as we also stumbled across this. So what I could see is that this is a quite complex problem and there are two different problems:
- Removing of all styles: in
Styles.tsxthedangerouslySetInnerHTML={{ __html: '' }}is missing which causes the styles will be removed after the client rehydrated the code (this is easy to fix). At least that was an issue we encountered (I will create a PR for that) - Flicker: There is the use of
ThemeProviderwhere thethemegot hooked in (this is the main problem of this issue and actually not quite easy to fix with a static site.
I will elaborate more on issue 2. (Flicker):
When styled-components changes the themes between light and dark it certainly also changes every class where the theme is attached such as here (redocusaurus changes theme.schema.arrow.color in the theme config, ergo the entire classname is totally different, I will come later to the problem of this).
So now there is the rendering of docusaurus website which is only once per buildtime and usually it is only for the light theme (since there is usually only one build). There is a smart logic of redocusaurus to provide the CSS for light AND dark theme into the HTML and the CSS classes are also prefixed with either html:not([data-theme='dark']) or html([data-theme='dark']) which would only work if the class names are the same in light AND dark theme (well I guess you see the problem now). But this is not the main reason for the flicker. The main reason for the flicker is the tiny JavaScript added by docusaurus to add data-theme="dark" as soon as possible, even before the main JavaScript is parsed and executed (you can see the script right after the <body> tag ). With that script, the dark theme styles will take action, but it does not quite match with the classnames of the document itself -> hence flicker.
Well, how can this be solved?
- Best would be to add css-variables and let only change the css-cariables, unfortunately this does not work as
redocdoes usepolishedand it will go 💥 on places like this.- To make it work: Try to remove
polishedas much as possible inredocand use things likergba(var(--some-color), 0.8)instead oftransparentizeand then update everything to css-variables. This would allow to remove the theme switching in redocusaurus and just change the css variables (with this there is also less HTML for free as only one stylesheet needs to be shipped 🥳)
- To make it work: Try to remove
- Easiest, but well, not the prettiest: the solution from @mfaux to just don't display it until it is fully rehydrated.
FWIW. I don't think there are any other options left, as styled components would work best with SSR but docusaurus is relying on SSG.
@JPeer264 Thanks for the detailed deep dive on this! I have merged your PR. And it's out in v1.6.1.
For the second part I don't think redoc would implement a solution for a downstream package, so I'll keep this issue still open.
Another thing I think which is caused by this issue is that it makes it so the scrollYOffset redoc option isn't applied properly on a page reload. On the first page load it applies the offset correctly, but when I reload the page the offset disappears, so when I scroll down the page the search bar becomes hidden by the navbar.
Adding @mfaux's CSS fix somehow fixed this problem for me.