Site performanceTechnical SEO

The first screen jumps: how we tracked down a font-induced shift and what did not work

On 17.08.2026 an audit showed a CLS of 0.2283 on our website’s desktop homepage. The cause turned out to be the font: the ch unit in a paragraph and a skewed decorative band. We break down which tips measurably did not help, what got us to 0.0056 and how to check your own site.

September 16, 2026
9 min read

On 17 August 2026 we were running an SEO audit of our own website. Speed-wise, the homepage looked fine: on a 1920×1080 desktop the first content appeared in 820 ms. But the same line of the report showed a CLS of 0.2283, that is, the cumulative layout shift of the first screen. The mobile run (390×844) on the same day showed 0.0000.

A reminder of the thresholds: up to 0.1 counts as “good,” above 0.25 as “poor.” So 0.2283 formally only “needs improvement,” but it was very close to the poor zone. Repeat runs within the same audit gave 0.2284, so it was no coincidence.

Below we explain how we looked for the cause, which common tips measurably did not help, and how to check the same thing on your own site. First, the limits: all lab figures were taken on the homepage of our own website in Chromium at 1920×1080. On a different template and with different fonts the numbers will differ, but the search method will still work. The audit took the starting 0.2283 on the live site over the network, while 0.0056 and the 3 September measurements were taken on a local production build, with no network latency to the site.

What exactly was jumping

The audit recorded a single jump at 739–777 ms after the start of loading. The right-hand block of the first screen, holding the heading and a paragraph, shrank in height from 507 to 468 px. Everything below it moved up 39 px along with it.

The cause of the jump was a font swap. The page is first drawn in a fallback system font, and once the brand font finishes loading, the browser redraws the text (font-display: swap). These fonts have different metrics: line height, letter width. Because of this, the text takes up a different amount of space, and neighboring blocks shift.

Why nobody noticed

The first reason: there was no shift on phones, and almost everyone checks the homepage on a phone.

The second reason is field data. For the 28 days up to 7 September, PostHog showed a p75 CLS of exactly 0 both on mobile (40 measurements) and on desktop (24 measurements). Only visitors who accepted cookies were counted. The sample is small, and most of that window falls before the fixes. In other words, the problem simply did not show up in the field data. Our assumption (we did not measure it separately): on a repeat visit the font is already in the browser cache and arrives before the first paint, so there is no swap.

The third reason: the jump lasts a fraction of a second. A developer with a “warm” cache does not see it.

Step 1: fallback font metrics (17.08)

The same day we did the obvious thing: added a fallback @font-face with vertical metrics taken from the font file itself rather than from memory (ascent-override, descent-override, line-gap-override).

A repeat frame-by-frame measurement showed this was not enough: the first-screen block still shrank from 511 to 468 px. We had aligned the line height, but the jump barely changed.

Step 2: the ch unit (03.09)

The second cause was found in a single class. The paragraph had a width limit of max-width: 40ch. The ch unit is the width of the “0” glyph in the font that is active right now. So while the text is set in the fallback font, the width is one thing, and after the swap it is another.

Here is what the frame-by-frame measurement showed:

Moment max-width Paragraph height Lines
915 ms 360 px 112.2 px 4
938 ms 400 px 84.1 px 3

The container became 40 px wider, the paragraph lost a line, and the block slid. We replaced 40ch with a fixed 400px: in the brand font this is the same width, so the line length did not change for the reader. After that, the block went from 483 to 468 px instead of from 511. The remaining CLS was 0.0640, already in the green zone. Still, we wanted to understand what was holding even that remainder.

Step 3: the skewed decorative band

The remainder came from a decorative band with outlined words, skewed via skew. For a skewed element, the height of the bounding box equals the line height plus the word width multiplied by the tangent of the angle. So any change in text width turns into a change in height. During the font swap, the three words of the band changed height like this: 207→212, 203→199 and 179→186 px. By shift area, the band outweighed all the text on the first screen.

We also ran a control experiment: we blocked the font files completely. CLS became exactly 0.0000, so the shift was 100% caused by the font, and there was nothing else to look for.

Next we tested the standard tips. Each one was checked by measurement, not by eye:

What we tried CLS
size-adjust in the fallback font 0.0640 — no change
line height in pixels 0.0640 — no change
font-display: block 0.0930 — worse
font-display: optional + preload 0.0056

size-adjust aligns the average line width, but on specific uppercase words the error remains. Line height in pixels does not help, because the skew adds width back into height. block gave the most interesting result: while the font loads, the text is invisible, but the browser calculates its space using the fallback font. When the font arrives, the swap happens anyway.

font-display: optional worked. The browser waits for the font for about 100 ms, and if it has not arrived, the fallback stays until the page finishes loading, with no swap. But on its own it would have ruined the design: the font almost never arrived in time, and the band would have been drawn in the fallback Arial. The measured band width was then 839 px instead of the brand 753. So we added <link rel="preload"> for the font file so it starts loading together with the HTML rather than after the CSS is parsed. With that, the band measurably has the brand 753 px, and there is no shift.

The final measurement: three runs, a local production build, 3 September. On desktop, CLS 0.0056 with LCP 876 ms; on mobile, CLS 0.0000 with LCP 768 ms.

We applied optional only to the decorative band. You cannot do this for body text: the reader would see one font or the other depending on network speed.

What the fix cost and what we found along the way (07.09)

The preload turned out not to be free. 4 days later, PageSpeed on mobile named this very band as the largest element of the first screen (LCP). And its font, a full 22 KB file, was being preloaded on every page of the site. The band needs only 17 glyphs, so we cut a 2.8 KB subset out of that same file.

During the same investigation, the network requests revealed two more fonts, 58 and 57 KB. They loaded on every page, although no style on the site used them: the package remained connected in the root layout, even though nobody read its variables. We removed the import and checked that the text looked the same.

How to check your own site in 15 minutes

You will need Chrome and a desktop window. Check mobile mode separately: in our case the problem was present on only one of the two.

  1. Open DevTools, the Network tab, and turn on Disable cache. This way you see the page through the eyes of a new visitor.
  2. Go to Performance and record a reload. The Layout shifts track will show every shift and the elements that moved. You can also get by with the console by pasting this after the page loads:
new PerformanceObserver(list => {
  for (const e of list.getEntries()) {
    if (!e.hadRecentInput) console.log(e.value.toFixed(4), e.sources.map(s => s.node));
  }
}).observe({ type: 'layout-shift', buffered: true });
  1. Run a control experiment. In Network, right-click the font file → Block request URL (or set the pattern *.woff2 in Request blocking) and reload the page. If the shift is gone, the font is to blame, and you can leave images and banners alone.
  2. Set the Font filter in Network and list all the font files. Then, in Elements → Computed, look at Rendered Fonts on several text blocks. A file that is not among the fonts actually used is a candidate for removal.
  3. Search the first-screen styles for the ch unit in widths, as well as skew or rotate on blocks with text. Both turn a font change into a size change.

We only measured our own Next.js site with self-hosted fonts. In an OpenCart theme with Google Fonts the swap mechanism is the same, but we have no figures for that case.

What these figures do not mean

  • 0.0056 is a lab measurement on a local build, not a promise for every visitor. Network, cache and device change the picture.
  • We cannot credit the zero in the field data to the fixes: most of the window falls before them.
  • On 12 September the site’s main fonts changed. The fallback metrics for the new fonts were calculated with the same method, but we did not repeat the frame-by-frame measurement of the first screen for this article. All the figures above refer to the site before that change.
  • CLS is one of the Core Web Vitals metrics. A stable first screen removes a technical defect; it does not raise rankings by itself.

How we handle the other metrics is described in the section on Core Web Vitals, and servers and page weight are covered in the article on website speed for business. If the first screen on your site jumps and the cause is not obvious, take a look at how we approach website speed optimization.

Tags

PerformanceSEO

🤔Did you like the article?

Your opinion helps us create better content

Share with friends

Found something useful? 🚀

Help others learn about it - share the article on social networks

https://lionex.com.ua/blog/pershyj-ekran-strybaye

💚 Thank you for helping us grow

Vladyslav Chystiakov

Writes about what he builds himself: online stores on OpenCart, applications on Next.js, integrations and site speed. The articles carry measurements and checks a reader can repeat on their own project, not general advice. Commercial development since 2015.

Frequently asked questions

Answers to common questions on the topic

CLS is cumulative layout shift: how much the blocks of a page jump during loading. Up to 0.1 counts as “good,” above 0.25 as “poor.” On our desktop homepage on 17 August 2026 the audit showed 0.2283, that is, “needs improvement,” almost right at the edge of the poor zone.

Run a control experiment: in Chrome DevTools, block the font files (Request blocking, pattern *.woff2) and reload the page with the cache disabled. In our case, after blocking, CLS became exactly 0.0000. If your shift disappears the same way, the font is to blame, and you can leave images and banners alone.

While the font loads, the text is invisible, but the browser calculates its space using the fallback font. When the brand font arrives, the swap happens anyway. On our homepage this made CLS worse: 0.0930 versus 0.0640.

We did not do that. For body text it is bad: depending on network speed, the reader would see either the brand font or the fallback. We applied optional only to the decorative band and added a preload for its font so it arrives before the first paint. Otherwise the band would almost always have been drawn in Arial.

We do not promise that. CLS is one of the Core Web Vitals metrics, and a stable first screen removes a technical defect. But rankings depend on many other things. Besides, our 0.0056 is a lab measurement on a local build, not a guarantee for every visitor.

Get the best articles by email

Subscribe to our newsletter and receive useful tips, insights and news about web development, marketing and business.

We respect your privacy. You can unsubscribe at any time.

Related articles

All articles
Одна літера в адресі: 76 перейменованих сторінок і шість днів 404
Technical SEO

Одна літера в адресі: 76 перейменованих сторінок і шість днів 404

19 серпня 2026 року ми перегенерували 76 російських адрес, бо в межах одного шляху співіснували дві транслітерації, — і майже шість днів старі адреси відповідали 404, бо механізм переїзду шукав сторінку лише за українським slug'ом. Через п'ять тижнів 12 старих адрес досі у видачі, а на юридичній сторінці сума за місяць і тижневі дані кажуть різне. Розповідаємо, чого з цього не випливає і як перевірити свій сайт.

8 min
Sep 24
Read more
We Lost Our Own Store Along With the Domain: What the Web Archive Knows
Technical SEO

We Lost Our Own Store Along With the Domain: What the Web Archive Knows

On 17 September 2026 we recounted in the web archive what losing our own store along with its domain looked like from the outside: 2,378 captures of the home page in March 2022, the first 301 on the first of April, the last capture of our content on 18 May. We show what the archive restores and what it does not.

9 min
Sep 17
Read more
Passengers on every page: a whole language's dictionary, a second header, and a chat nobody asked for
Site performance

Passengers on every page: a whole language's dictionary, a second header, and a chat nobody asked for

On 5 September 2026 we took our own site's response apart line by line and found five things that shipped to every visitor's browser although nobody used them. On 13 September a sixth turned up. Here are the bytes before and after, a lesson about characters versus bytes, and five checks for your own site.

10 min
Sep 17
Read more