External component libraries using emotion not rendering styles on first load

In my project I have an external component library I use for quite a few parts of my site. I recently refactored this to use emotion CSS in JS and it has been working nicely in all other projects that use it, however in Frontity I am getting a strange issue with styles on initial load.

When the page first loads my components, none of the emotion injected styles are active on the components, and then after a second they apply. I don’t know how else to describe the issue as no errors occur.

You can see the issue by visiting this link and hard refreshing a few times: https://gfw-blog-headless-e1nmpzhhg.now.sh/

This library is working fine in other SSR and none SSR apps using the global styles and regular components. Could this be due to the fact frontity does its own thing with emotion?

Any thoughts would be very welcome!

Hi @e.j.a.brett, thanks for your question.

This effect is known as a flash-of-unstyled-content (FOUC) issue, and it’s indeed related to the use of CSS-In-JS and emotion

Can you share a repo with the code of your project so we can test it and check what may be causing this issue in your project?

Hi @juanma thanks for getting back to me! The project is in this repo: https://github.com/Vizzuality/gfw-blog-headless/tree/feature/update-gfw-components. You need to checkout the branch feature/update-gfw-components which is having the issue.

The components that are having issues are those imported from our components library here: https://github.com/Vizzuality/gfw-components which is also built with emotion.

Let me know if I can provide anything else.

Hey @e.j.a.brett! :wave:

This is Michal from the Frontity team. I ll have a look at this issue tomorrow and will let you know if I need any more information. Cheers! :slightly_smiling_face:

1 Like

Great! Thank you for taking the time!

Hi @e.j.a.brett

I"m sorry but I was not able to run your project.

I checked out the feature/update-gfw-components branch like you mentioned earlier, ran installed dependencies and ran npm run dev.

The app fails with:

ReferenceError: document is not defined
    at A (webpack-internal:///./node_modules/gfw-components/dist/bundle.js:1:593210)
    at p (webpack-internal:///./node_modules/gfw-components/dist/bundle.js:1:594492)
    at u (webpack-internal:///./node_modules/gfw-components/dist/bundle.js:1:593147)
    at LboF.e.exports (webpack-internal:///./node_modules/gfw-components/dist/bundle.js:1:594830)
    at Object.Ut/D (webpack-internal:///./node_modules/gfw-components/dist/bundle.js:1:931367)
    at r (webpack-internal:///./node_modules/gfw-components/dist/bundle.js:1:124)
    at Object.Z5cx (webpack-internal:///./node_modules/gfw-components/dist/bundle.js:1:1017304)
    at r (webpack-internal:///./node_modules/gfw-components/dist/bundle.js:1:124)
    at Object.FT44 (webpack-internal:///./node_modules/gfw-components/dist/bundle.js:1:444996)
    at r (webpack-internal:///./node_modules/gfw-components/dist/bundle.js:1:124)

So, it looks like you are referring to document in the your component library and it’s failing while doing server side rendering. I did a quick search on github and looks like you are referring to document in a couple of places in the component library without checking first that document exists: https://github.com/Vizzuality/gfw-components/search?q=document&unscoped_q=document

Ah I am sorry you ran into issues! I took a look and the reason for the error is we use yarn with a yarn-lock so when you ran npm i you installed more recent versions of the lib which seem to have caused this issue. gfw-components has two methods for being installed, one as a normal npm module, and another as a script that gets injected into the document (for non react sites). These two bundles lives completely separately so I will see what is going on with the document error. It should not be present in that built at all.

I have pushed a commit to fix the version of this lib to an older one which should remove any document references and allow you to work with npm instead of yarn.

If you still have the patience do you think you would be able to try again? :sweat_smile:

Thank you for taking the time.

Hey @e.j.a.brett, no problem, I was able to pull and run the project again this time with no issue :slight_smile:

I can see that the styles are not attached to the <head> on the server because I receive the initial HTML without the styles.

However, I didn’t find a moment to look into it in depth today and troubleshoot this so you’ll have to bear with me a little longer :sweat_smile:

Hi @e.j.a.brett,

I took another look at this and I can see that some of the styles are attached on the server, however, some of them are missing.

For example I can put the breakpoint here:

And dump the HTML from the html variable into a file. I can see that the there are some CSS classes referred in the html, e.g. .css-ap9cid-RowDiv that do not have the declarations in the CSS.

@luisherranz Do you have any more insight on this by any chance?

Thanks for taking a look! Yes, it appears that any styles loaded by emotion from my external components library are not added on the server, and the ones in frontity are. That css class .css-ap9cid-RowDiv for example comes from my component library that is made with emotion.

They are added when the client first renders which causes the flash. Would this be caused by frontity managing the import of styled and css from its own package? import { styled, css } from 'frontity'?

that’s all true, unfortunately :slight_smile:

This was my thought as well - that perhaps this is not compatible with how you define the externals in you webpack configuration https://github.com/Vizzuality/gfw-components/blob/master/webpack.config.js#L106

I have made a small amount of progress! The short answer is that it seems you cannot use @emotion/styled in an external component library with frontity due to the alias defined in frontity’s webpack config. As soon as I migrate a components css to the css prop in my component library it works fine.

First I tested to see if the effect happened with another component library made with emotion. I tried react-select and everything worked fine.

I also noticed that my <GlobalStyles /> component I was importing from my library was present on the serve also. This is a component that uses the emotion core Global component and I was creating this inside my lib so frontity’s instance of emotion wasn’t doing this.

Then I rebuilt a component with the css prop and the styles showed up in the server.

I also tested my library in a default Nextjs app and this issue is not occurring with the emotion packages defined in the externals as you mentioned above and using styled package either.

Is there anything I can do about this? I know you can customise the webpack config for frontity yet, but I would say this kind of thing will be limiting for others in the future if you can’t have external libs using @emotion/styled.

Thoughts?

@e.j.a.brett you are creating a bundle outside of Frontity with Webpack. Don’t do that! Your bundle will have everything duplicated (react, react-dom, emotion…) and things are going to break, like Emotion SSR. That’s not the way you should create a component library for any React framework, not only Frontity. The React framework needs to be the one doing the bundling.

I’ve tried using

import { XXX } from "gfw-components/src";

in your repo but it doesn’t work because of this Webpack alias. For that reason, doing things like alias is usually a bad idea because you couple your code to a specific Webpack bundling and now you cannot use that code outside.

As soon as you are able to import the unbundled JavaScript files in Frontity, Emotion SSR should work just fine, even if you use the libraries directly imported from the emotion packages instead of frontity. It’s not even an alias, it’s just a re-export.

I’m not sure why the css prop is working with SSR, maybe it’s due to how Emotion is processing that in your Webpack bundle, but you should never create a bundle yourself embedding all your dependencies inside.

Finally, your bundle (gfw-components/dist/bundle.js) weights 2Mb right now. This is going to be reduced once you import the unbundled JavaScript files because it will remove libraries like react, react-dom and emotion. But those libraries aren’t that big. It looks like you are also embedding a lot of fonts and images in the bundle. Be careful with that. We’ve seen that it impacts the performance score on LightHouse. Once you let Frontity take care of the bundling, it’s going to generate different files for those images/fonts in the static folder. That is fine, it’s what we want. You can run npx frontity dev -p and inspect the HTML generated at build/analyze to see the modules included in the final bundle.

So my suggestion is to switch to importing gfw-components/src directly (get rid of the alias). Let us know what happens, also with the final size of your bundle :slightly_smiling_face:

2 Likes

Thank you for the detailed response @luisherranz. Very helpful. You are totally right, I shouldn’t be importing with the packages bundled in. Changing that has fixed the issue. Thanks for all the help and for leading you on small goose chase :sweat_smile:.

In doing so I did find something again related to using external component libraries. The same bug you are having with semantic UI: https://github.com/frontity/frontity/issues/405, I am having with my component library due to the alias you are setting for lodash-es in your resolve config. I managed to fix it by adding lodash-es to my peer deps and setting an alias for both lodash and loash-es to the external node module, but thought I should flag with you guys anyway.

Thanks for all the help!

1 Like

I’m glad the issue is solved @e.j.a.brett :slightly_smiling_face:

Yes, you’re totally right about lodash-es. At that time it seemed the only way to make lodash work with tree-shaking but it doesn’t seem like a good option.

May I ask what was your exact problem? What were you importing from lodash that didn’t work? (to make sure we fix that as well).

It makes sense yes!

For me the issue was I had lodash as a dependancy in my external lib, and in frontity also. But I didn’t has lodash-es as a dep in my lib so the imports failed even though it was declared as an external dependancy but webpack.

The solution was to install both lodash-es and lodash on my external lib and create an alias to lodash-es as you have in frontity.

I see. Thanks @e.j.a.brett :slightly_smiling_face:

We removed the alias and the fix will be released in the next version: https://github.com/frontity/frontity/pull/456 so I guess that will solve your problem as well.

1 Like

Excellent! Thanks!