If you don’t know, CLS is one of the main factors in SE now. It stands for Cumulative Layout Shift. Google on web.dev has explained it in detail. But, even following all the guidelines, the CLS score is still nearly 1.
How I solve this weird CLS problem? Let’s inspect them one by one.
Images without dimensions
The page has no images. So, this doesn’t apply at all. I have checked the Network tab on the Chrome dev console. There are no requests for any pictures.
Ads, embeds, and iframes without dimensions
It does not have any of them either. No ads, embeds, or iframes. Nothing. I’ve suspected some elements in the page header, but the Performance tab on the Chrome dev tool says the moving one is the body itself!
Dynamically injected content
Web Fonts causing FOIT/FOUT
Yes. There’s one custom web font from Google font. I have removed it to test. But, the CLS score only improves to nearly 0.5. Still far beyond the ideal line, which is 0.25.
Actions waiting for a network response before updating DOM
This one is a bit complicated. But, I have made sure there are no such actions. There are no AJAX requests on that page.
Back to Google. Most of them are saying similar things to those on the web.dev page. I almost give up at this point. Fortunately, I don’t.
I inspect other similar pages from the other sites, looking for the one with the lowest CLS score.
This effort proves to be effective. I find the one with a 0 CLS score.
After inspecting that page with a 0 CLS score thoroughly, I realize one different thing. The page I am working on loads of all styles (CSS) inside the body tag to fix the render-blocking problem. That page loads them inside the head tag. So, I give it a try.
Here’s the fix that works for me: I move all styles from the body tag to the head tag. Then, I re-test with the Performance tab on the Chrome dev tool.
The Performance tab does not show up any content shift errors now. Therefore, I re-test it on the Google PageSpeed Insights page. The result is surprising.
The CLS score for both mobile and desktop becomes 0 all the time!
From the user’s experience, a CLS score nears one is not good. But, if the score nearly zero or completely zero, it’s the best because there are no content shifts.
Here’s what I experience as a user for this page with the CLS problem.
- As a user on the slow network, I see the page renders basic HTML without any styles on the initial page load.
- Then, I can interact with that page without style, scrolling down as an example.
- Suddenly, the stylesheets near the bottom of the body close tag come in. I see the content on my screen suddenly changes drastically.
- The text I am reading moves somewhere at the bottom of the page. Now, I see another text on my screen because the CSS has applied the style on this page.
As a user, I see this experience as an annoying one. The CLS, as I understand it now, measures this kind of user experience.
But, putting those stylesheets in the head tag will cause another issue: the render-blocking problem. Can we have the best of both worlds? Yes. We need to apply the critical CSS approach. See the conclusion below.
It’s a surprise. I used to hear and read advice to put the stylesheets at the bottom. Put them inside the body tag after the other elements to avoid the render-breaking problem. Perhaps, I need to dig into this more.
Doing such a thing causes this weird problem: CLS issue more than 0.25. Therefore, the fix seems simple. Just put them back into the head.
However, this simple fix only works for a simple web page with a few stylesheets. For a more complex web page, we’ll need to:
- extract the critical CSS for that web page,
- put them into a separate file,
- then place that separated file in the head section and leave the rest of CSS in the body tag at the bottom.
This way, the CLS score will stay as low as possible. Yet, the render-blocking problem will be minimal as well.
Lessons to learn
- Don’t give up so easily.
- I often find a solution by going humbly.
- Since the CLS score is showing up as one of the web vital components in Google Search Console, I decided to prioritize it over the render-blocking problem.