How to reduce DOM nodes in React’s web application effectively

Vu Thanh Nguyen
NE Digital
Published in
7 min readNov 11, 2020
Photo by Florian Olivo on Unsplash

At NE Digital, we are working continuously to improve the users' online shopping experience on Fairprice Online which is a high-traffic online grocery in Singapore.

When we talk about web performance improvements, we have three core things that need to be concerned: Javascript bundles, image sizes, and DOM nodes. To push our website loading faster, we should reduce these sizes in our data loads as much as possible but at the same time, it also should not break UI on the client-side.

This topic is very broad for a short article, so I’ve decided that DOM nodes reduction will be the main discussion in this article.

First of all, I’d like to explain a bit about DOM nodes and why it impacts our web performance.

What’s DOM? Why it impacts our websites?

DOM (Document Object Model) represents an entire HTML in memory. You can simply understand that HTML will become plain text and we cannot access nodes/elements without DOM. These DOM nodes get parsed by the HTML parser on the client-side, so it means we have more elements in data loads, client machines need to work more for loading and parsing them.

In fact, users rarely see whole pages, which have full of HTML and all DOM nodes, in the first loads. Especially, this could be a big deal to mobile users who have fewer capacities like less CPU than desktop users, so we should remove initially unseen web content from nodes which is to make our first rendering faster, and eventually, it also improves critical rendering path.

To start our discussion about the excessive DOM size, I’d like to highlight two common mistakes which often happen on numerous responsive websites below:
- Rendering components according to devices or screen sizes
- Rendering the DOM nodes which will be hidden in the DOM tree

Rendering components according to devices or screen sizes

This strategy is great in loading components with respective devices like desktop, tablet, and phone that has been applied by a number of websites. Especially, websites aren’t fully responsive because of UX and designs of different layouts. From that point, we need to develop different components according to devices for the same feature and hide/show them particularly.

Let’s use my case study of the promotion page. We have 2 distinct layouts of filter and sort which are for desktop and mobile with different designs.

The filter and sort of Fairprice Online — The left is a mobile design and the right is a desktop design

Initially, we simply hid and showed these components with media queries in CSS that caused various unexpected hidden elements in browsers but they were still existing in the DOM tree. In other words, users who were using mobile devices also got many desktop elements in page loads instead of mobile-only elements.

Before the DOM nodes reduction, it has 5,929 elements

The above report is exported by Google Lighthouse, which measures web performance and proposes solutions for common issues, on the product listing page of Fairprice Online. A report has higher scores which means it’s better. I could recommend that you should read the article in the below link to have a better understanding of the huge performance impact of DOM size.

Before thinking about solutions, I’d had two things in my mind:
- It should not break our media queries and device breakpoints which have been used in the entire codebase
- It should adapt perfectly with server-side rendering which is NextJs

And then I came up with the below implementation which has been working great on both the client-side and server-side. (Note that as the article limitations, I cannot show all functions in detail, but I have left many comments, please reach out to me if you need more details)

The key points in the component:
- isInitiallyRendered is to decide child components will be rendered initially on the server-side or be later on the client-side. This flag is only applicable for server-side rendering frameworks like NextJs and Gatsby
- Converting all media queries to optionKey (from, below, and only) and deviceKey (mobile, tablet, and desktop). For example, @media screen and (max-width: 1023px) will be converted to optionKey (below) and deviceKey (desktop), which means this element style is handled by screens are smaller than the desktop’s screen size
- Implementing a resizing event with a debounce timer for cases that users change screen sizes on the fly. The considerate case, in reality, is users are rotating their tablets from horizontal to vertical

After cutting off unexpected rendered elements in the DOM tree with the above component implementation, we were able to reduce the DOM size from 5929 elements to 2303 elements, which is over 50% improvement. As a result, the performance score also increased from 43 to 66 in the Lighthouse report. That was a big jump for us!

After the DOM nodes reduction, it has 2,303 elements

If the above solution is not good enough for your project setup, I would propose another approach. We can use User-Agent, which is a part of a request header and sends essential client info to the server, to detect users’ devices and then rendering components accordingly. However, I couldn’t maintain media queries with this proposed solution in the current codebase, so I didn’t use it, but it’s sort of promising for server-side rendering frameworks or fresh projects.

Rendering the DOM nodes which will be hidden in the DOM tree

The fact is, in most cases, once users open the websites, they can only see above the fold content at the beginning. We usually tackle this problem with back-end strategies such as pagination, data truncation, streaming APIs, and so on, but I’d like to focus more on front-end strategies in the scope of this article.

The below example is the product carousel on the home page of Fairprice Online. We can see that the carousel has more products than the screen size/viewport limit. If we render all these products on browsers, they will spend much more effort in the DOM tree processing, in spite of users cannot see all products initially. That will be worse for mobile users with capacity restrictions.

The product carousel on the home page of Fairprice Online

To solve that problem, we have a technique, which is called windowing. That word is to describe the way we render data to fit in the window (screen or viewport) for the first time, and the remaining data will replace the current shown data while we keep scrolling up/down or left/right. Ideally, the remaining data will be kept in memory and UI will be changed according to users’ actions. This concept is not new to web developers, especially React developers who have experience with virtual DOM.

I’d like to project the DOM tree differences between windowing and non-windowing with the same dataset of 1000 items in those below images. The left image shows a huge DOM tree of all elements aligned with 1000 items; The right image shows a few elements of 1000 items which are fit in the window and will be updated frequently by users’ interactions.

The left is without windowing, the right is with windowing

After expressing that problem, I’d like to introduce 2 libraries react-window and react-virtualized which have made their own reputation among web development communities.

I cannot say which one is better than another, many developers have still had controversial discussions about them. In my opinion, if you consider library size, you can pick react-window; if you consider utilities, you can pick react-virtualized.

On the other hand, those libraries have not been supported for server-side rendering because they are still dependent on window and document objects of the client-side which are not available on the server-side (at least at the moment I’m writing this article), but we can work around with loading skeletons to make UI rendered smoothly and UX better.

A loading skeleton on Fairprice Online

To have better insight, I’d like to show a demo in this live code that has been presenting element lists with and without windowing. Note that I’m using react-window for the below demo, but the approach will not be different from react-virtualized.

Those libraries have their own document in detail and many good articles mentioned them, so I won’t dive into them too much. If you’re keen to understand more, I recommend that you should give them tries first and then you will get amazed at how it boosts your website faster with impressive DOM nodes reduction.

Last words

Thank you for reading my work to this point. The entire article is about my personal experience and my own opinions that would not fit all readers with different experiences. Therefore, I’m willing to receive all your feedback to improve it gradually. That will be a great help for other readers to have better views.

Last but not least, hopefully, after reading it, you also can find out something useful for your projects as well as your technical knowledge.

--

--