daily.dev optimized post page performance by addressing issues with excessive DOM size and JavaScript execution on pages with heavy user interactions and comments. Utilizing Chrome's content-visibility property and lazy rendering comments with the IntersectionObserver API, they significantly improved page responsiveness and reduced main thread work. The result was a drastic improvement in Lighthouse scores, enhancing user experience and SEO.
Recently in daily.dev we started to work more on some initiatives related to SEO (Search Engine Optimization). One of those initiatives was to improve the performance of our post pages. All of our post pages are prerendered on demand with ISR (Incremental Static Regeneration), cached on CDN (Content Delivery Network) and they are generally really fast. What we noticed however is that pages with a lot of user interactions, long discussions and many comments generally perform worse than the rest. Since those pages have a lot of value we wanted to bring them on par with the rest of the pages. As with any performance analysis, the first step was to fire up the Chrome DevTools and start profiling. We ran the Lighthouse audit and noticed that we had a few issues where we could improve. Some of those we already had in our backlog for post pages but it was interesting to see what are the specific issues that trouble the post pages with a lot of comments. This article explains how we solved those issues to boost the performance of our pages. ๐
Measuring performance โฑ๏ธ
The first one was a warning about excessive DOM size. This is a common issue with big web pages that have a lot of content. The more comments users post, the more DOM elements we will have because we have to render comments content, user images and then also any comment can have any number of nested comments. This can quickly grow the DOM size and slow down the page.
The second issue was related to JavaScript execution and main-thread work. Partially this was due to React hydration and the amount of DOM nodes that need to be parsed and added to React's virtual DOM. Another issue was that our comments support markdown RichText format. Any comment can have images, links, mentions and similar formatting. To support things like mentions we have to parse the incoming HTML that was rendered on the server from markdown, hook up the dynamic elements like mentions and then render them in our React components.
Identifying the problem ๐
Before doing any kind of performance optimization the good practice is to validate the fix before implementing the full-blown solution. What we could do before optimizing any part of our web app was to just not render comments at all and see how the page performs. After running the Lighthouse audit again performance improved significantly and both of the issues from above were gone. This confirmed that comments were the main issue and we could start working on the solution. What we wanted to do here is to try and solve issues on the frontend. This was because it is easier to test and validate on the frontend versus moving more of the RichText processing to the server or adding comments pagination. Those would probably be better long term but would require a lot more engineering effort and would push back some more important features on our roadmap. We always prefer to move fast and iterate on the solution.
Fixing excessive DOM size
The first thing that was solved was the amount of DOM elements we had to render. We utilized an awesome browser feature with content-visibility
CSS property on our comment DOM elements. We set it to hidden
which tells the browser to skip rendering the comment elements until they are scrolled into view. From MDN:
The element skips its contents. The skipped contents must not be accessible to user-agent features, such as find-in-page, tab-order navigation, etc., nor be selectable or focusable. This is similar to giving the contents display: none.
It is important to note that this feature currently only works in Chrome (and Chromium-based browsers) but support is expected to come soon to other browsers. Since a lot of our users use Chrome we were happy to use this feature as a progressive enhancement. Even with that change, our main thread was still busy. This was because our JavaScript code was still parsing the comments data so it could be rendered to DOM. This also highlights one of the negatives of using any kind of JavaScript frontend framework, you always pay some main thread work while rendering to DOM. This is not a blow to avoid using a frontend framework, it is just a tradeoff that we have to be aware of.
Optimizing comments rendering
We decided that even though we load all the comments for our posts immediately we will try to lazy-render them as they come into view. For example when the user is scrolling. Of course, we want to show the first few comments immediately so we played a bit with a threshold and arrived at rendering the first 5 comments (and their sub-comments) immediately and then lazy-rendering the rest. This also allows search engines to index the part of the discussion. We also plan to add structured data for comments in the future so that search engines can understand the comments better without us rendering all of them immediately. To support lazy rendering we used the IntersectionObserver
API. This API allows us to observe when an element comes into view and then we can trigger some action. In our case, we trigger the rendering of the comment and its sub-comments.
While testing the solution we noticed that the performance improved significantly. The main thread work and the DOM size were drastically reduced. One issue we encountered was that our comment link sharing did not work. We allow users to copy links to specific comments in the discussion. For now, we fixed it by detecting comment hash links and in those cases the lazy rendering is disabled. This will allow us to not be penalized for performance and our users can still use comment deep links.
Result
After implementing the solution we ran the Lighthouse audit again and the performance really did improve significantly โ . We also noticed that the page is much more responsive while loading. We are happy with this solution and as said above we will continue to monitor the performance and iterate on the solution. We already have some more candidates like minimizing our JavaScript bundle and optimizing different images of our posts and inside comments. This will help to also bring our mobile score to this level. but that is a story for another time.
We tested the solution on some of the biggest discussions we have on the platform and you can see the final result below. If you are interested in more details you will be happy to hear that our repository is open source so you can check out the pull request here. Enjoy! ๐