The Browser's Main Thread Is Expensive
When you hear "frontend optimization," many of us immediately think of reducing network requests, minimizing the bundle size, or implementing effective caching strategies. While these aspects are undoubtedly crucial, the main thread is often overlooked, despite its critical role in the browser's performance. On screens with substantial interaction and live data streams, the main thread can become a bottleneck, causing screen freezes, delayed button responses, and delayed typing in search boxes. This phenomenon is referred to as "jank."
The browser consists of multiple threads, but nearly all code we interact with from our end operates on the main thread. This thread is responsible for computation, rendering, event handling, network response handling, and the execution of your framework's internal processes. The main thread can become expensive, especially when dealing with intricate tasks.
The main thread's workload can be categorized into two primary areas. The first is executing JavaScript code, which includes user-written code, event handlers, timer callbacks, network response callbacks, and framework internals. These tasks are executed in the order they enter the queue, with no priority given to their timing relative to the screen refresh cycle.
The second aspect involves screen drawing. When the Document Object Model (DOM) or styles change, necessitating a screen update, the browser follows a series of steps, typically skipping them if no changes occur, to produce a frame. Only the final compositing stage, which assembles the output on the screen, is handed off to the compositor thread.
For a smooth visual experience, frames must be drawn at the display's refresh rate. On a typical 60Hz display, this translates to 60 frames per second, or approximately 16.6 milliseconds per frame. However, not all of this time is available for use. After accounting for the browser's own processing costs, developers often consider around 10 milliseconds as the practical budget for each frame. This constraint becomes particularly problematic when confronted with long tasks.
JavaScript was originally designed for a single-threaded event loop model. The main thread processes one task at a time, blocking other activities while a task is running. If a JavaScript function takes 200 milliseconds to execute, during this period, the browser cannot repaint the screen or respond to user inputs. Tasks exceeding 50 milliseconds are generally considered problematic.
To illustrate this concept, consider a demo where pressing a button causes JavaScript to monopolize the main thread temporarily. During this moment, the JavaScript animation halts, and typing into an input field becomes unresponsive. Conversely, a CSS animation continues unaffected. This disparity arises from the main thread's handling of different types of tasks.
Optimizing performance on the main thread can be approached in two main ways. The first involves managing the main thread's time efficiently from within. The second approach is to offload work outside the main thread altogether. The first strategy encompasses four core practices: splitting, batching, prioritizing, and deferring. These techniques help in managing the size and timing of tasks within the main thread.
Written by urgent.news from Lobsters's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.