We recently worked with Cloudinary to rebuild their blog. There was a big focus on performance throughout the process, especially passing Core Web Vitals. However, we recently started seeing poor Cumulative Layout Shift scores on a number of posts. I set out to investigate.
Cumulative Layout Shift is a metric that tracks how much content on the page shifts while the user interacts with it. These layout shifts can be a real pain for people trying to use your site:
Have you ever been reading an article online when something suddenly changes on the page? Without warning, the text moves, and you’ve lost your place. Or even worse: you’re about to tap a link or a button, but in the instant before your finger lands—BOOM—the link moves, and you end up clicking something else!web.dev
Our Real User Metrics showed that desktop users were often experiencing unacceptable levels of layout shifts.
To better understand the types of layout shifts our users were experiencing, I used the dev tools to throttle my connection and highlight layout shifts. With page loading slowed down, I could see and understand the layout shifts:
I could see that the overall layout was shifting during the page load. The width of the main post container shifted a couple of times, causing the whole page to re-render.
After some debugging, I realized a couple of seemingly unrelated choices were combining to trigger these layout shifts:
chunits to constrain our post content.
- Optimizing our web font loading for a “Flash of Unstyled Text”
ch unit is equivalent to the width of the
0 character in the currently selected font. This makes it really helpful for typographic fine-tuning. For example, you can use it to apply a max-width to your prose content to cap the overall line length to improve readability.
When using a web font, the browser needs to decide what to do while the font is loading. By default, the browser will not display the relevant text until the font has loaded. This is called a “Flash of Invisible Text,” and it means that visitors can’t start reading your content until the web font has loaded, which can make the page feel slower.
Luckily, there are other options. We were optimizing for a “Flash of Unstyled Text.” This means that the browser immediately displays our text in a fallback font that’s already loaded on the visitor’s computer. When our font loads, it then gets swapped in to replace the fallback.
This ensures that visitors can start reading content sooner, but when the font swaps in, it can cause layout shifts. I thought this might be contributing to our Cumulative Layout Shift, but it didn’t account for the intensity of layout shifts we were seeing.
In our case, we were using
ch units to define our page layout. This meant that our post content was equal to
50ch units (or the width of fifty
0 characters all lined up in a row).
But the width of the
0 character differs from font to font. So when our web font was swapped, the width of 1ch changed, which impacted our layout:
- The page loads and displays using fallback fonts.
chis equal to the width of the
0character in the fallback font.
- We asynchronously load our web font (in this case, Inter) and swap it in.
- Now the
chunit is equal to the width of the
0character in our web font.
- Since the post content is set using
chunits base their size on the
0character in the current font, the post layout shifts with the font change.
Here’s a CodePen showing the issue. The font is swapped every two seconds. Since the post content is set using
ch units, it changes too.
To fix the issue, I swapped out my
ch units for
rem units, which stay consistent regardless of font. This small change resolved our layout shift issue without affecting the overall layout.
We will also want to look into using tools like the Fallback Font Generator and CSS rules like size-adjust to adjust our fallback font to be sized more closely to our web font. But, for now, this quick fix avoids severe layout shifts for our users.
ch units are really neat! But watch out for layout shifts when using them.