Achieving Peak Performance 🚀: Harnessing the Power of Redux Sagas in Large-Scale Frontend Optimisation
Optimize, Streamline, Excel: The Redux Sagas Advantage for Frontend!
Introduction
In today's fast-paced world of web development, delivering high-performance applications at scale is a challenging endeavor. As the demands on front-end projects grow, optimizing performance becomes paramount to ensure exceptional user experiences. Today, we will dive deep into how we at Rocketium tackled performance issues by employing Redux Sagas, an indispensable middleware for Redux.
The Challenge: Performance Bottlenecks and Complexity
At Rocketium, we handle images and videos, editing, and generation on a massive scale. Our web application encounters the demanding task of managing 10,000 creatives, in a single session. With a high volume of custom position and style changes for every element of the creative, we need to ensure optimal speed and performance is imperative.
As our web app grew in complexity, incorporating new features and heavy operations, we encountered performance issues. We observed lags, delays during dragging and dropping, and overall performance hindrances. Additionally, crashes and memory issues became more frequent, leading to a sluggish user experience. The number of re-renders per action reached around 40, even affecting components unrelated to the specific action and the FPS dropped to almost 0 on almost every action, causing significant challenges. 🤯
Setting up: THE GOAL
Our relentless pursuit of excellence and unwavering commitment to providing a seamless user experience propelled us to embark on a transformative journey of UI optimization. We recognized the critical importance of enhancing the performance and responsiveness of our web application to meet the ever-growing demands of our users. Our primary goals were:
(G) reater performance and responsiveness: Our aim was to deliver a highly performant web application capable of handling the massive scale of image editing and generation tasks.
(O) ptimal stability and reliability: Crashes not only disrupt user workflows but also tarnish the reputation of our product. We made it a top priority to eliminate crashes, avoid FPS drops and provide a stabile web app. Our commitment to quality and reliability drove us to ensure a rock-solid foundation for our web application.
(A) gile memory management and effective error handling: Memory leaks and inefficient error handling can lead to significant performance degradation and compromise the stability of our application. We aimed to proactively identify and resolve any memory leaks, ensuring optimal resource utilization.
(L) esser Re-renders: Excessive re-renders can result in sluggish UI interactions and hinder the overall performance of the web application. Our goal was to bring down the number of re-renders to zero or, at most, one per relevant change.
We had to deep dive into the intricacies of frontend development to fulfill our goals.
The First Step: Identifying the Root Cause of the Issues
At Rocketium, we adhere to the principle of identifying the root cause of any issue before diving into the implementation phase. This approach allows us to gain a comprehensive understanding of the problem at hand, enabling us to develop effective solutions. When it came to optimizing the performance of our web application, we followed this principle diligently.
To kickstart our journey, we utilized various tools such as Lighthouse and Google's FPS (Frames Per Second) tracker. These tools allowed us to measure and record the performance of our web application accurately. By analyzing metrics such as load times, rendering speed, and overall responsiveness, we gained valuable insights into the areas that required optimization.
By leveraging these pre-optimization data, we not only gained a baseline performance measurement but also had a clear understanding of the existing pain points. This data-driven approach ensured that our subsequent iterative changes were focused on the right areas and guided us towards the most impactful optimizations.
Starting with a thorough analysis of our web application's performance, we set the stage for a systematic and targeted optimization process. Armed with insights and a clear understanding of the issues at hand, we were ready to dive into the technical aspects and implement the necessary changes to enhance our web application's performance and user experience.
During this analysis, we were able to pinpoint several key root causes contributing to the performance issues:
Irrelevant Dependencies: We discovered that many components were unnecessarily dependent on modules and listening to state variables that were not required for their functionality. This dependency on irrelevant data led to unnecessary re-renders and negatively impacted the overall performance of the web application.
Coupled Business Logic and UI: Over time, the components in our application had become tightly coupled with both the business and operational logic, as well as the data received from APIs. This coupling resulted in the components being subscribed to a multitude of variables and local states. Consequently, even a minor change in one of these variables triggered re-renders throughout the component hierarchy, leading to significant performance degradation.
Accumulation of Re-renders: Given the heavy operations performed by our web application, the accumulation of re-renders over time proved to be a significant challenge. As more and more re-renders occurred, the browser tab experienced memory leaks and ultimately crashed, causing disruptions for our users.
Identifying these root causes laid the foundation for our subsequent optimization efforts. Armed with this knowledge, we could devise targeted strategies to address each issue and optimize the performance of our web application.
Finding the Right Solution
After identifying the root causes of our performance issues, we realized that a combination of modularization and separating concerns (UI and logic) would be the key to solving our problems. We recognized that breaking down components into smaller, more focused modules would address some of the issues related to code coupling and unnecessary re-renders. However, we also needed a solution to handle complex asynchronous operations and manage side effects efficiently.
After careful evaluation, we decided to incorporate Redux Sagas into our architecture. Redux Sagas is a middleware library that specializes in managing asynchronous flows in Redux applications. It provides a way to separate complex logic from components and reducers, offering a more structured and organized approach to handling asynchronous operations.
Addressing the Performance Challenge: Strategies and Implementations:
Let's take the example of a component responsible for handling the position change of an element in an image. This was the existing code that was causing performance issues. It may seem simple, but actually, there are a lot of dependencies that we need to change in order to calculate the new coordinates and update them to show them in our editor’s preview. Notice how all the selectors that are irrelevant and unwanted are also imported and how the logic is present in that same file.
Modularization: Separating Components, Sagas, and Reducers
We created a separate module for every component/sub-component. The file structure looked something like this:
To address the intertwined nature of our code, we embarked on a journey of modularization. Here's how we de-coupled all the logic, separated the business layer, and made the component modular:
Moved business logic to saga
After de-coupling it from the main rendering file, we moved the logic for calculating the new coordinates and dimensions of the element to the saga middleware.
Utilizing Redux Selectors for Efficient State Retrieval
We extracted the Redux selectors to a separate file to minimize unnecessary re-renders and utilized them within the sagas. This approach allowed us to efficiently retrieve data from the state without triggering unnecessary updates to the UI. By implementing this optimization technique, we reduced the computational overhead and enhanced the performance of our web application.
Note: The code snippets provided are simplified examples for illustrative purposes. Actual implementations may vary based on specific requirements and application structures.
Outcome:
In addition to the notable performance improvements, our UI optimizations provided several other benefits:
Lighter and Faster Application: With re-renders reduced to either 0 or 1 and rendering occurring only when necessary, our application became significantly lighter and faster. Users enjoyed smoother interactions and experienced improved load times, resulting in heightened productivity and satisfaction.
Streamlined Debugging: The modularization of components, business logic, and the clear separation of concerns significantly streamlined the debugging process. Developers could swiftly identify and resolve issues within specific modules, resulting in faster troubleshooting and issue resolution.
Accelerated Onboarding: The optimized codebase, with its modular structure and clear separation of responsibilities, facilitated faster onboarding for new team members. The streamlined architecture and organization improved code navigation, allowing newcomers to quickly grasp the project's structure and contribute effectively.
Enhanced Scalability: Redux Sagas provided a structured approach to handle complex asynchronous logic, making it easier to scale the application as it grows. The encapsulation of side effects in sagas enabled better code organization and maintainability, allowing the application to evolve seamlessly.
Conclusion:
Through the implementation of Redux Sagas and TypeScript, we successfully overcame performance bottlenecks, lags, and crashes within our web application. By modularizing components, separating concerns, and employing robust error handling, we transformed our application into a lighter, faster, and highly optimized platform. These optimizations not only improved the user experience but also expedited the debugging process and eased the onboarding of new team members.
We take great pride in our commitment to continuous improvement and the adoption of cutting-edge technologies. As pioneers in the field of creative automation, we offer an exhilarating and challenging environment for talented developers. If you are passionate about building high-performance applications and delivering exceptional user experiences, we invite you to join our team and become an integral part of our journey to new frontiers in web development.