Recent Blogs

Visualizing browser bottlenecks in frontend development

A Frontend Performance Odyssey: Unraveling the Browser’s Bottlenecks

June 23, 20254 min read

Crafting a frontend transcends the mere creation of a visually captivating user interface brimming with myriad features. It demands an equally steadfast commitment to delivering a seamless and fluid user experience. A subpar user experience can precipitate a 2% surge in monthly churn—a steep toll that no business can afford to overlook.

In the realm of CRM platforms like HighLevel, where sales teams and customer support reps rely on swift access to customer data, suboptimal UI performance can be catastrophic. A delayed response to a lead or a slow-loading customer profile can mean the difference between closing a deal and losing a client. HighLevel’s journey commenced seven years ago, and since then, we’ve scaled at an exponential rate, doubling in size annually. This meteoric growth necessitated an unwavering focus on accommodating an ever-expanding customer base and database, relegating backend development to the forefront of our priorities.

In the meantime, our customers had voiced their concerns over the suboptimal UI performance. This frustration intensified as we transitioned to a micro-frontend architecture in our quest to modernize the application. That is when I embarked on a formidable endeavor to confront the frontend performance challenges that had impacted our application for quite some time.

The Architectural Foundation

Our tech stack comprised:

  • A monolith constructed in Vue 2

  • Over 60 microapps in Vue 3, seamlessly integrated via module federation plugins

  • More than 200 exposed modules from these microapps

This architecture empowered our developers to adopt a contemporary design language and harness the advanced capabilities of Vue 3. However, an overlooked consequence emerged: the substantial browser memory demands imposed by the mounting and unmounting of these microapps—a silent saboteur of performance that we could no longer ignore.

Our adoption of micro-frontends was a strategic move to enable team autonomy and accelerate feature development across our 60+ microapps. By leveraging module federation, we could integrate these Vue 3 microapps into our Vue 2 monolith, allowing for a unified design language and modernized functionality. However, this came with significant trade-offs. The dynamic loading of over 200 exposed modules introduced considerable runtime overhead, with mounting and unmounting operations consuming excessive browser memory.

A Promising Start

To gain deeper visibility into our app’s performance, we turned to a combination of Sentry, Chrome DevTools, and Lighthouse. We focused on key metrics like First Contentful Paint (FCP), which revealed delays in rendering critical UI elements, and memory usage, which highlighted the excessive footprint of our microapp lifecycle. Additionally, we monitored Core Web Vitals—particularly Largest Contentful Paint (LCP) and Cumulative Layout Shift (CLS)—to ensure our optimizations aligned with user-perceived performance. These tools and metrics became our compass, guiding us toward the root causes of our UI challenges.

To address this pervasive performance issue, I’ll outline the pivotal strategies that enabled us to reduce the app’s overall memory consumption by an impressive 30%:

  • Clearing Timers on Component Destroy. Unmanaged setTimeouts and setIntervals in our components were lingering in memory long after their lifecycle ended, especially in dynamic microapp transitions. By implementing a cleanup in the beforeDestroy lifecycle hook (or onUnmounted in Vue 3), we ensured these timers were cleared, reducing memory leaks by approximately 5%.

  • Pruning Unused Computed Values. Our components were littered with computed properties that were rarely used but still recalculated on every state change. By auditing and removing these, we reduced unnecessary reactivity overhead.

  • Assigning Unique Dynamic Keys to keep-alive Components. The keep-alive directive cached components without unique keys, leading to stale instances consuming memory. Adding dynamic keys ensured proper cache invalidation, freeing up resources.

  • Transitioning from v-show to v-if. Using v-show kept elements in the DOM, contributing to a bloated render tree. Switching to v-if ensured unused elements were entirely removed, cutting down on DOM overhead and improving rendering speed.

The Transformative Outcomes

The impact was profound: we achieved a 30% reduction in memory usage across the entire application—accomplished through optimizations on a single page.

These practices are indispensable for every frontend developer aiming to build resilient, high-performing applications. To future-proof our platform, we’ve instituted stringent quality gates to prevent such issues from reaching production in future pull requests.

While our optimizations yielded impressive results, the true lesson lies in prevention. To safeguard against future performance pitfalls, we’ve integrated performance budgets into our development workflow, ensuring no pull request exceeds predefined memory or load-time thresholds. We’ve also embedded Lighthouse audits into our CI/CD pipeline, catching regressions early. For any team embarking on a similar journey, I advocate fostering a performance-first culture: prioritize user experience from the outset, empower developers with the right tools, and ensure manual coding is paired with rigorous validation. Frontend performance isn’t a one-time fix—it’s a continuous commitment.

JavascriptEngineeringBrowser Memory
Back to Blog