How to debug cumulative layout shift by adding secret visuals to HotJar recordings

Sergii Grebeniuk
Preply Engineering Blog
4 min readNov 9, 2020

At Preply, we’re building a platform that connects language students with the best online tutors. One of our most important parts of our product is the tutor search page, where students can apply filters and browse profiles to find the tutor that best meets their needs.

Because Google’s performance metrics are going to affect our position in search results soon, we wanted the tutor search page to appear as fast as possible in Google Search Console. In particular, we were concerned about the cumulative layout shift (CLS) metric. Ours was around 0.4. We had to decrease it to 0.1 for 75% of our page loads, which we were able to do using several tools.

Tracking CLS in real-time

First, we mimicked the Chrome User Experience Report with web-vitals. This step was necessary because CrUX didn’t allow us to query the data for specific URLs aggregated by day.

We were then able to monitor our CLS in real-time. Using the results, we discovered a few obvious issues that we could easily fix. These optimizations reduced our CLS to 0.2.

Our CLS chart in Grafana
Our CLS chart in Grafana

We had several guesses about what to optimize next, but it was hard to predict which fix would be most impactful because there is so much happening on the page. In addition to filtering and paginating, users can book lessons, chat with tutors, and more. Customers often spend hours on the page, accumulating CLS before navigating elsewhere.

Logging CLS triggers

Web-vitals allowed us to identify the exact DOM nodes that were causing the biggest CLS. The list revealed several areas where we could try to reproduce issues:

The elements triggering the biggest layout shifts
The elements triggering the biggest layout shifts

We did another series of fixes that reduced our CLS to the desired 0.1. At this point, we thought our work was finished and we could move on to more pressing issues.

However, two weeks later, the CLS spiked to 0.15. Although we could see which DOM nodes were causing the issues, we were struggling to reproduce them based on the data from our dashboards.

HotJar to the rescue

We use HotJar recordings to better understand our customers’ behaviors. These recordings appear as reconstructions of our actual DOM with our original CSS.

While looking for new ways to fight the CLS spike, we discovered an interesting undocumented detail in the HotJar implementation, which we could use to insert debugging information into the recordings while keeping it invisible for our users.

HotJar reproduces all the recorded DOM mutations with our original CSS applied. Their player also adds a hotjar-css-hover class name to our DOM roots in order to override the mouse events while we are viewing the users’ journeys:

HotJar reconstructs our DOM and styles, adding .hotjar-css-hover to our DOM roots
HotJar reconstructs our DOM and styles, adding .hotjar-css-hover to our DOM roots

We used that class name to start highlighting all the layout shifts. We also displayed the current CLS value, updated in real-time as users interact with the page. Here’s an example.

The issue:

The shadow around our filters is implemented in a CLS-unfriendly way

The corresponding HotJar recording:

The layout shifts are now highlighted in purple
The layout shifts are now highlighted in purple

Here is a closer look at the highlighted areas:

The highlighted layout shifts
The highlighted layout shifts

Here is the CLS indicator:

The CLS indicator on the bottom right of the recording
The CLS indicator on the bottom right of the recording

Note that the debugging information stays invisible to our users!

Here is the code which does that:

With the data from our recordings, we were able to reproduce a couple of CLS issues that we would have missed using only pure logs.

After we fixed the issues we discovered, our CLS is back to green, now at 0.09 for 75% of our page loads.

--

--