 Performance after load has always been of interest to the web developer community. We've all seen microbenchmark comparisons of browser APIs, talked about the ups and downs of DOM performance. But there's more to this topic than just API speed. But before we start, what even is loading performance? When we talk about performance on the web, we are actually often talking about loading performance. And that is the time between the initial navigation, clicking a link or launching an app, and the app being ready to use so the user can interact and perform primary functions off that app. We've often focused on how to optimize the performance of everything that falls into that time frame. And that is important because people abandon pages if they don't load quickly. And there's no point in optimizing stuff that happens after load if people never make it to that point. How much a user will weight varies. If the value of an app is already known to the user, it's a super fun game or it's your email client, people might sit through minutes of loading. In general, though, we're talking about load times on the order of seconds. There's also another thing to consider, which is that sites like blogs and newspapers and content sites generally use full page reloads for navigation. As a result, optimizing for load is actually the right thing to do because the user is constantly affected by it. Most interactions are page loads. Something like a web-based email client, on the other hand, is typically loaded once and stays open forever. Loading data from API endpoints when a user interacts with the app. Even in cases where interactions feel like a navigation, switching inboxes, clicking on a message, those are typically done in JavaScript to preserve client-side state. And that means that they're still considered part of postload performance. Loading happens once, the rest is postload. And your app is probably somewhere in between these two extremes. Some things are full page navigations for the proper page load, and other things are more SPA-like behavior with dynamically created DOM. Depending on where your app is on the spectrum, you need to decide how to split your optimization efforts. And that split is also worth revisiting as your application grows because it can change. As web apps get more complex, the main focus will typically shift from load to postload. And this means that those postload interactions become increasingly important since users are doing more things within each page. These interactions are what we're measuring when we talk about performance afterload. So how do we measure performance afterload? What metrics can we track in order to quantify the postload performance characteristics of our app? We know how to measure loading performance. We can measure LCP, FID, and all that other stuff. I mean, that's what web vitals are for. Right. And one of the main principles of Core Web Vitals is that they measure user experience. And while the web vitals initiative is interested in postload performance, it turns out that measuring the user experience impact of postload performance is very difficult. Anecdotally, a user experience is something like power drain. Our phone's battery runs out faster, and that's a negative experience. It's a lot harder to think of how a user might experience something like memory footprint. Can we feel RAM usage? I know that my memory is definitely getting worse with age, but I don't think my phone's is. It's much more likely that the secondary consequences of high memory pressure, like the device freezing up repeatedly to free up memory, are the things that are noticeable to the user. Also, what we might consider acceptable memory usage on one device could be way too much for another. And that makes it really hard to come up with a viable generalized metric for memory usage that translates to its impact on user experience. So we don't know what the relationship between memory consumption and user frustration is. We do know that it's probably indirect. What the Core Web Vitals Initiative is looking to quantify are the concepts of smoothness and responsiveness, which we already know are important factors for user satisfaction and frustration. Before we dig into these, though, I want to know that web vitals for postload performance are an ongoing research project. What we are presenting here today are ways how you can measure smoothness and responsiveness in a way without having to wait for Core Web Vitals to come up with a canonical metric. We already know these factors matter, and you should be optimizing them today. Smoothness is an application's ability to keep up with itself. A lack of smoothness makes apps feel low quality and can cause frustration when users see changes lagging behind or animations trying to play catch up. To quantify smoothness, we're looking for consistency. That's frames being rendered at a stable rate based on the device's screen refresh rate, often 60 frames per second. Also, when there's nothing to render, it's actually good to hit zero frames per second. Those aren't missed or dropped frames. They're actually avoided wasted frames because we had nothing to draw. One potentially useful measurement strategy is to calculate yourself a smoothness score. If a frame ship's on time, you get a point. If a frame ship's too late, you would deduct points depending on the delay. If you can feel the agony looking at the button on the bottom, that would get a low smoothness score. So to measure smoothness, we could run a ref loop and track how much time elapses in between the individual calls. In particular, we want to pay attention to long frames that take too long to render. At the same time, you also want to allow long frames when there's nothing to render and that shouldn't hurt our score. It is important to note that this ref approach is actually really bad. It forces the browser to ship every single frame even when there is nothing to render. So you would never see that zero FPS optimization kick in. In your app, you usually know which parts of the code trigger rendering. It's a lifecycle event or a state change and so on and so forth. So instead of running the ref loop the entire time, you could limit it to just those situations. But it's still quite the brittle approach. Instead, there is a proposal for a new type to be added to the performance observer API which will allow you to track how long frames take to render. And this would avoid this forced frame loop issue. But it should still be combined with your knowledge of the app's specific logic so that you don't collect huge amounts of unnecessary data. The other user experience aspect of post-load performance is responsiveness, which measures how quickly a user interface responds to interactions like clicks, taps, and keystrokes. Responsiveness is something that's easy to explain because it's something we feel. A modal dialogue takes too long to open or characters we're typing are taking a second to show up. It's much harder to come up with an algorithm that measures responsiveness. And that's because responsiveness is specific to each application and its particular user interface. An algorithm might consider the spinner shown on a sending button to be a reaction. But to the user, we know that the real reaction is when the sent notification appears or a message composer closes. As developers, we also have this information. We can use performance.measure to measure the time delay between an action, like the send button click, and its corresponding reaction, the message being queued for sending. Remember the rail guidelines here? Aim to respond to interactions in 100 milliseconds or up to one second if loading things from the network. Later on, callingperformance.getentriesbytype to return your customer responsiveness metrics is a great way to then siphon them over to logs. Measuring your app's important actions and reactions also makes it possible to investigate how those interactions are being affected by other technical performance metrics, like long tasks or slow renders. If you're using a framework to implement your interactivity, there may even be ways to automate some of that tracking. Just make sure that the measurements still represent the time between an action and its true user-facing reaction. So you might be asking yourself, will smoothness and responsiveness be part of Web Vitals in 2022? The answer is maybe. But as I said, we already know that these factors are important and we should be optimizing for them. So how and what do we even optimize? As with any recommendation, it's impossible to overstate the importance of measuring things yourself. Generalizations are suggestions at best. Real, effective optimizations come from gathering data about your application to be optimized. To that end, the first optimization is to set up measurement so you can make decisions informed by real data. Measuring synthetic data through microbenchmarks and lab tests can't really give you an accurate picture of how things will work for real users. In order to make optimization decisions based on data, it's best to get that data from those users. Apply the measurement techniques that we showed in production and send that data to someplace where you can analyze it later. Phil Walton has a great guide on how to set this up at the link. Now with your fresh data in hand, you know where your bottlenecks are. So what techniques can help with typical post-load performance problems? Well, one step is to free up the main thread. We need to limit the amount of code that we run on the main thread because we share that thread with the browser to do all the rendering work, like calculating styles and layout and paints. And ideally, the main thread is used in a way that the most expensive non-interruptal operation that we see is that rendering work. And one very direct way to free up the main thread is to move some of your applications code to another thread using web workers. Workers are really well suited to heavy tasks like parsing or analysis. Just remember to be careful that the code that you move is not responsible for immediate UI changes. You don't want to end up having to wait on a worker operation to complete in order to show an initial UI response. Moving code around can be tricky, but there are some nice solutions available these days. With Comlink, for example, you can offload a heavy module from the main thread to a worker while still being able to work with this API as you're used to. In addition to web workers, new Worklet APIs also provide a way to register self-contained modules that handle a specific task and the browser will run them on the most appropriate thread for that type of task. Paint Worklet is one of these and it lets us offload Canvas drawing code to the browser's rendering process instead of slowing down the main thread. In this example, we're rendering a material inking effect and the only main thread code is an event handler that captures most coordinates. There are many ideas in this space to make JavaScript better suited for concurrency. Aside from the module blocks proposal, though, there are largely ideas in the heads of individual engineers and we don't want to over-promise anything by showing you code that may or may not ever work. The point here is that there are people working to bring concurrency to JavaScript and making it a lot easier. The third strategy for optimizing postload performance is to reduce your memory usage. At a high level, we know that both smoothness and responsiveness can be affected by the amount of memory a web application consumes. This is tricky to quantify because there are a number of different ways to allocate and retain memory on the web and they each have a different impact on performance. Also, the thresholds and scale of those impacts depend on external factors like hardware and other software. However, there are a few general conclusions we've been able to draw based on our research so far. We studied the effect of memory consumption on total blocking time and found that there's a threshold where increased memory usage actually degrades performance. This suggests that we should avoid excessive memory usage. We often view memory as an inexhaustible resource, but it's not. On some devices, that big chunk of data might be a significant percentage of the devices available memory. Also, consider where you're caching. Remember that we can rely on the network cache and the network cache is smart about deciding between caching and memory or on disk depending on usage heuristics. Finally, monitor for memory leaks. This can get fairly tricky, especially for code that's referencing DOM trees or iframes or browser windows. There's an article here that includes five different ways to find and fix memory leaks in those tricky cases. Despite us working really hard so far to distinguish noting performance from post-load performance, they do actually share some optimization techniques. Loading performance boils down to one key principle, only load what is needed. This applies equally to post-load performance. While you don't do a full page navigation in a post-load scenario, you still load stuff like data and probably code to handle that data. So what you need to do is to split your data and your code so you only load what you need. Preload what's next in idle time, defer invisible content and don't inline assets into your bundles. Finally, a topic I never seem to tire of talking about, make sure you're shipping modern code. Modern JavaScript is supported in 90 to 95% of browsers and is generally much faster than the equivalent transpiled code. The biggest performance improvements here typically come from avoiding automatically injected polyfills that aren't needed in modern browsers. You can also ship modern code without dropping support for old browsers by serving fallback bundles using the module no module pattern. If it's been a while since you've last looked at your transpiler configurations, that's a good place to start improving your post-load performance. So to recap, make sure you measure your apps' responsiveness and smoothness with your app-specific knowledge and make sure you do that in the real world and not just in the lab. Once you know where your bottlenecks are, try and fix them by freeing up the main thread, reducing your memory footprint and shipping modern JavaScript for modern browsers. And that's it for our whirlwind tour of post-load performance. Thanks, you can find all the articles we mentioned down in the video description.