Blog
Engineering
7
minutes

How to Fix a Freeze Between React Pages Swap - with Dom, VDom, Painting

When you build a SPA product one of your first preoccupations is to keep everything fast. You click, and you get instantly a feedback. If you click on a link, you want to navigate instantly to the desired page. If when you click you have to wait for some time before feedback, the user experience quickly becomes horrible.
Benjamin Debon
Software Engineer
Summary
Twitter icon
linkedin icon

At Qovery, we take great care about the global reactivity of our product and in this article, we are going to dismantle the last freeze between two pages that we experienced because we learned many interesting things during the fixing process.

null

The Problem: a Significant Page Freeze

As you can see in the video, in our interface, there are two tabs: Overview and Deployments. Pay attention to the mouse click, can you see what’s wrong here? That’s right, there is a gap of about 400 milliseconds between the actual click and the moment the Deployments tab shows its content.

null

400 milliseconds might sound like it’s nothing, but in terms of browsing experience, it is HUGE. In order to fix this, we need to understand why switching from one tab to the other takes so long.

First investigation

There aren’t that many elements on the page, so let’s do it the hard way, by commenting each component until we can identify the culprit. If we can find the component that is responsible for the delay, we will then be able to inspect it, and hopefully figure out the underlying cause (could be a Javascript issue, among other things).

First things first, let’s comment the big table. My intuition tells me that it might be a good suspect.

null

Bingo! Commenting the table component made the tab switch instant, meaning there is definitely something going on with that table.

Deeper Investigation: a Rendering Issue?

Knowing that the table is the key, let’s read its code, as well as the code of its parent. Alas, we cannot find any Javascript code anywhere that could cause such a delay. No blocking <await>, no big data computation, no nothing. In terms of code processing, nothing fishy whatsoever.

Hold on, what if the freeze was linked to a rendering issue? I have already experienced something similar in another project. I’ve learnt that, when trying to render a table with a huge amount of rows, the browser can sometimes freeze the whole tab to focus on the rendering part of the job.

During that kind of freeze, as a user, you can’t see any CSS animation, or any Javascript animation. The rendering process is simply blocked. You don’t get any feedback from your browser. The page is completely and utterly frozen.

It seems like it’s exactly what’s happening here. The browser freezes everything while it is striving to render and paint this big new table node element. And once it’s done, everything magically goes back to normal.

To keep this article a short and easy read, we are not going to dive too deep into why the browser behaves like this. If you want more insight into this, I recommend reading this article : Inside look at modern web browser (part 3) on the Chrome Developers blog. Insanely interesting.

Now, back to our situation. What do we do now?

First Approach: Fragmenting the Rendering Process

A quick team chat provides us with a likely solution. Here’s what we’re thinking:

If directly printing the big table freezes everything before the tab is actually shown, let’s just start showing the tab change, AND ONLY THEN let’s pass some data to the table to start to render the big object. As long as we have given feedback to the user that the tab has changed, and we have displayed the loading placeholders, then it’s okay if the browser freezes for some hundreds of milliseconds.

This approach seems rather cool and easy to implement!

Spoiler:

It failed miserably.

But it did lead to some really interesting React behaviors…

Implementing our Newfound Solution

The table is waiting for some data.

When we initialize the parent (the deployments tab), we initialize data as an empty array [].

At that point, our brains are going like this:

Ok, at the first render, the table is gonna receive an empty table, so the browser is not gonna freeze and it’s gonna render our deployment tab. Hurray.
Then, inside a React useEffect, we fill up data with whatever we want (we want to recall to you that <useEffect> is always executed after a render, React will never execute your useEffect before rendering)
null

So we have:

  1. Rendering the deployment tab component for the first time with empty data (no freeze)
  2. After first rendering <useEffect> is called to fill up the data
  3. The data has been filled up, React can re-render the component with the big Table

Aaaaaannnd… It did not work.

Why Things Didn’t Go According to Plan

How odd. The Javascript execution follows the procedure to the letter. A quick console.log() method allows us to view the first render with empty data, followed by the useEffect, and then the second render with the data filled up.

And yet, the issue is still there, we can see it with our own two eyes. After we click, the browser freezes, and then the whole <Deployments tab> is displayed, including the completed table. What happened to the middle step featuring an empty table?

null

The answer to that question is rather technical, and has been wonderfully covered by Vladimir Klepov in his article useEffect Sometimes fires before paint. To put it simply, even if React renders its code in its Virtual Document Object Model (DOM), the order to paint the real DOM is not always sent to the browser at every React render. Sometimes, React can simply decide to postpone the real DOM painting a little bit, if it judges that it will perform better like this.

In our case, React only sends out the order to paint the actual page after our useEffect, when the data is filled up. This is why we still experience this terrible freeze.

All You Need is a Timeout

To force React to paint our Virtual DOM with empty data, in the useEffect, let’s add a very short <setTimeout>. A timeout that will last only one cycle, but will be enough for React to render our tab before rendering the Deployments Tab with the table.

null

In the following video, you can clearly see the two-step rendering:

First: the tab activation.

Second: the table rendering.

With this scenario, the browser still freezes, but the user experience is ten times better because, at least, the user action triggers instant feedback.

Final code with a combination of useEffect AND seTimeout

Improving performance and page speed is a full-time job when it comes to SPA projects. It is, most definitely, a highly interesting area of work, which can help developers learn a lot about how React and browsers work. And what’s even more rewarding is that the quality of your work in that area can directly impact the experience of thousands of users in a positive way.

Sometimes, you have to come up with hacks. Sometimes, you find a more elegant solution for the same problem a few weeks later. At Qovery, we believe that sharing our findings is the best way to discover even better ones. If you think our setTimeout workaround inside a useEffect is not the best way of dealing with this issue, feel free to share your own approach with us. We are always happy to share, and eager to learn!

Pssst… The repository of Qovery frontend console is open source. Come and have a look! ;)

Share on :
Twitter icon
linkedin icon
Ready to rethink the way you do DevOps?
Qovery is a DevOps automation platform that enables organizations to deliver faster and focus on creating great products.
Book a demo

Suggested articles

AI
Infrastructure Management
Product
5
 minutes
GPU workloads on EKS just got way simpler with Qovery

Running GPU workloads on EKS has never been easy, until now. With Qovery’s latest update, you can enable GPU nodes, configure GPU access, and optimize costs automatically, all without writing a single line of YAML or touching Helm charts. Qovery now handles everything behind the scenes so you can focus entirely on your applications.

Alessandro Carrano
Lead Product Manager
Kubernetes
 minutes
Kubernetes Deployment Strategies: Pros, Cons & Use Cases

Master Kubernetes deployment strategies: Rolling Update, Recreate, Blue/Green, and Canary. Learn the pros, cons, and use cases to choose the right strategy based on your uptime, risk tolerance, and resources. Simplify complex rollouts with automation.

Mélanie Dallé
Senior Marketing Manager
DevOps
Developer Experience
 minutes
AWS ECS vs. EKS vs. Elastic Beanstalk: A Comprehensive Guide

Confused about which AWS container service to use? This comprehensive guide compares the trade-offs between simplicity, control, and complexity for ECS, EKS, and Elastic Beanstalk to help you choose the right platform for your application.

Mélanie Dallé
Senior Marketing Manager
DevOps
AWS
7
 minutes
Migrating from ECS to EKS: A Complete Guide

Planning your ECS to EKS migration? Learn the strategic business case (portability, ecosystem access), navigate the step-by-step roadmap, and avoid common pitfalls (networking, resource allocation). Discover how Qovery automates EKS complexity for a seamless transition.

Morgan Perry
Co-founder
DevOps
 minutes
Fargate Simplicity vs. Kubernetes Power: Where Does Your Scaling Company Land?

Is Fargate too simple or Kubernetes too complex for your scale-up? Compare AWS Fargate vs. EKS on cost, control, and complexity. Then, see how Qovery automates Kubernetes, giving you its power without the operational headache or steep learning curve.

Mélanie Dallé
Senior Marketing Manager
DevOps
Cloud Migration
 minutes
FluxCD vs. ArgoCD: Why Qovery is the Better Way to Do GitOps

Dive into the ultimate FluxCD vs. ArgoCD debate! Learn the differences between these top GitOps tools (CLI vs. UI, toolkit vs. platform) and discover a third path: Qovery, the DevOps automation platform that abstracts away Kubernetes complexity, handles infrastructure, and lets you ship code faster.

Mélanie Dallé
Senior Marketing Manager
Qovery
 minutes
Our rebrand: setting a new standard for DevOps automation

Qovery unveils its new brand identity, reinforcing its mission to make DevOps simple, intuitive, and powerful. Discover how our DevOps automation platform simplifies infrastructure, scaling, security, and innovation across the full DevOps lifecycle.

Romaric Philogène
CEO & Co-founder
Qovery
3
 minutes
We've raised $13M Series A to make DevOps so simple, it feels unfair

I'm excited to announce our $13M Series A, led by IRIS and Crane Venture Partners with support from Datadog founders and Speedinvest. This investment will fuel our mission to make DevOps simple and scalable, expand in the US and Europe, and accelerate product innovation.

Romaric Philogène
CEO & Co-founder

It’s time to rethink
the way you do DevOps

Say goodbye to DevOps overhead. Qovery makes infrastructure effortless, giving you full control without the trouble.