Looking for more podcasts? Tune in to the Salesforce Developer podcast to hear short and insightful stories for developers, from developers.
68. Performance Tuning Critical Rendering Path
Hosted by Charlie Gleason, with guest Benjamin Harding.
Fine-tuning website performance is an iterative cycle: as more features are added, performance will degrade. Sometimes you can't optimize a website any further than you've already done, and that's okay. As long as you keep your user's perceived performance in mind, you can still get away with having spinners and splash screens, so long as they are receiving feedback and are aware that something is happening.
Links from this epiode
- Critical rendering path
- DOM and CSSOM
- Media attributes for CSS files
- Preloading / prefetching assets
- Heroku’sMalibu icon library
- Code splitting
- React Lazy and Suspense
- Visualising bundle sizes
- Tree shaking
Charlie: Hello and welcome to Code[ish]. I'm Charlie Gleason. I'm a designer and developer at Heroku, and today I am joined by Ben Harding, who is here from Raygun, to talk to us about performance tuning the critical rendering path. Ben, do you want to introduce yourself?
Ben: Yeah. Hi, I'm Ben. I work at Raygun as a developer there, mainly on the front end of course and today I'm excited to talk about the critical render path with Charlie.
Charlie: For people who maybe don't know what it is, can you describe the critical rendering path?
Charlie: And so in that case, why is the critical rendering path important?
Ben: So the reason why it's important is, the faster that we show these pixels, the better an experience they receive. So in a sense it's a form of accessibility, but also beyond that, if you're looking at the business metrics, 40% of users leave a page if it takes longer than three seconds to load. Page speed affects SEO, these days. You can have a look for the speed update and for the Google bots. And so users are constantly seeking feedback as quickly as they can.
Charlie: Yeah, I have definitely done that myself.
Ben: Yeah, so have I, many times.
Charlie: And I think on the stats of page load times, I remember seeing a stat and I can't remember exactly what it was but it was an enormous amount of money that was lost over small windows of time where the longer something took to render, the more profound the impact it had on potentially, say for an eCommerce site, how much they were selling or how much turnaround they had on purchases.
Ben: Yeah. I remember seeing a stat just the other week actually, that for every couple of milliseconds that a company improved upon, they would get 0.5% more return, more customers who are purchasing. So it does affect the bottom line. And there are many articles that have gone over this and how the performance can really impact how it should be treated as a feature.
Charlie: Yeah and I actually also mentioning accessibility. I think that's a really important point as well. The idea that not all Internet is created equal and having access to the same information, like that democratization of the Internet, like Internet as an open space means that for people who are in environments where they don't necessarily have the best access to Internet or unlimited data or any of those things, it does become... I think that's a really valuable thing to be concerned about or to want to optimize for.
Ben: Yeah, 100%. Funny enough, I'm actually on a terrible mobile plan and only get around a gig's worth of data, which isn't too much, but coming from New Zealand, that could be a lot.
Ben: Mobile plans aren't the greatest, but it seems to be like every month I run out of mobile data. And so, yeah delivering less assets, delivering less code does have an impact on the user and so whether they have to purchase.
Charlie: So if I was a person who had a website that was in the browser and I wanted to start to measure this to make it better, how would I go about measuring the critical rendering path?
Ben: So there are a number of metrics which browsers leverage and show us using the performance timing API these days, which we can use to start measuring to gain an understanding of how fast our website currently is. So many of you are doing these sort of like performance optimizations. The best approach is generally you measure first then optimize. That way you know your baseline and how much quicker you are making it. So a few different sets we can use. The first paint, first contentful paint, the load time, and even the time to interact is great.
Charlie: Yeah, nice one. So if people aren't aware of those four terms, so first paint is?
Ben: Yeah, so the first paint is the time it takes for the browser to render anything at all. So this could be a background color.
Charlie: And then the first contentful paint?
Ben: That's generally the time it takes for the browser to render something of meaning, some form of content. This could be an image, this can be some ticks on the screen. It's just anything that the browser deems as contentful. This is kind of one of those earthly stats where it's really hard for a browser to actually understand what it terms content and so it just takes a piece of text or an image, anything that is in your DOM that it forms as content or render it. It can be measured about the same as the first paint sometimes.
Charlie: And I think there's something we heard load time.
Ben: It's not a great measurement for the critical render path, but it's also useful to know as a whole, if you have a lot of marketing scripts, generally they're loaded asynchronously, and so knowing the total time it takes to load your application, your whole website is another measurement for how much data is being actually consumed or how much requests are being made.
Ben: So time to interactive is the measure of how long it takes for four seconds after the slowest task or the last long task. So it's kind of a measurement in which the user can now start interacting with the page without having some form of delays being slow, slowing them down. So clicking buttons they can start like engaging with your website rather than everything's still kind of not working because the main thread is just blocked.
Charlie: Actually that could be a good spot to step back and maybe talk a little bit about how the browser tends to interpret the data from a server and how it turns that into the page as well because I think these are really interesting in the sense of like how are you getting the things up in the fastest possible way, but if people aren't aware of how the browser itself does those things. I mean I think that could be really interesting.
Ben: Yeah, that sounds great. So the very beginning stage of the process, the browser needs to actually make a request to your server, wherever it's hosted. So it makes its request. The server generally has some process behind them unless it's a static website. And at the end return set HTML with any like response hitters or data that it needs around that. Once your browser and the client's machine downloads it then goes through what's called down passing.
Ben: And so it goes through the HTML line by line reading it and as it finds any link tags, script tags, images, it starts making requests to download those required assets. And these assets are what can block your actual web site from loading. But we'll get to that in a bit.
Ben: Once the HTML was fully passed, it would have constructed what people commonly know as the DOM, the Document Object Model and then once the DOM is made it then constructs what's called the CSS Object Model, the CSSOM, but I don't have a clue how quickly say that like five times. And it can build this render tree once the CSS object model and the DOM are constructed and that render tree is the combination of the two.
Ben: So it figures out these nodes on your website, that's HTML tags, use these CSS selectors and so therefore they need these styles. And then once it has this whole render tree created, it can then apply the initial layout, figuring out the position and size where everything should be laid out beside each other. Maybe you have some flex, box elements you have floating in other areas and then figures out where exactly to place them in relation to one another and then it can start painting it on the screen. So this whole complex flow is what your browser needs to do every single time it renders anything on the screen. And so trying to make their processes as silky smooth as possible.
Charlie: And if you were looking to measure these kinds of things, usually, say DevTools in Chrome, would be an option. Are there other things around that if you're trying to measure beyond in the browser?
Ben: Yeah, so there are a couple of tools available to us if we were trying to measure it, DevTools in the browser are a great step of course. It's easily accessible to anyone who's a developer, and even those who aren't. You can also use what's called Lighthouse. They have great tools available and even there are some real user monitoring tools out there, which you can inject a script onto your site. They then start tracking the weight request timings for you so they can track the first paint, generally contentful paint load times, all of that information and they can then display it.
Charlie: Yeah, that's great. So at this point we've got like an understanding of both how the browser interacts with these assets and displays them. I don't think I realized when I first started working as a front-end developer, just how complex that process is. Like you said, it's this huge flow and then we have these things that we can use to measure the critical rendering path. So what are some common optimizations? If I had a site that I then wanted to make more better?
Ben: If you want to make it better, common optimizations when you're trying to just make your website better. It's funny how people can kind of sometimes forget, but maybe it's a common tap that we do now is just minify your code, strip out all of the spaces, produce variable names.
Ben: You can throw it through an automated build process to do the step for you. Just minify your code so that you actually end up sending less. Browsers don't need to know how many spaces they are. They don't really need comments on them either. The code is the code to them. And so just removing any of that, just human side to it I suppose really help it deliver this less code. And that's where you can have slow snaps and things to help you debug it later on if you do run into a production issue.
Charlie: Sure. Yeah. And I suppose there's loads of tools that are out there for having that build step. I mean npm would have I'm sure a million. But are there any that you recommend?
Ben: It depends on your build setup. Like I know Webpack has quite a number of automated minification tools. If you have that set up in your pipeline in Parcel and all of them have their own minification steps, which you can just use, there's Uglify, I've used that a number of times in the past.
Charlie: I will put links to that in the show notes. Any other common optimizations that come to mind?
Ben: And another common optimization that developers should be doing, which I always have imagined is common sense but I've encountered in a couple of jobs where you go to a website, you start looking at the performance is to optimize your images. Done this a couple of times where I come to a website and it's just not optimized.
Charlie: Sure. I think there was a tweet going around last week that I will try and find for the show notes. There was a page on Google, it was one of their marketing sites and I can't remember which one it was, but I'll put a link to it. It all loaded like a 21 or 28 megabyte GIF, which is just like, wow. It seems amazing that at no point, I mean I can see how that could happen, right? But it's amazing that even with all of the tools that we use that, that can be missed. Like someone could just not realize, well not necessarily understand the implications of loading 28 megabytes of sweet GIF.
Ben: Yeah, GIFs are wonderful but sometimes they can just bloat.
Charlie: Yeah, Absolutely.
Ben: I can kind of understand sometimes and empathize with how that happened. Like we have a number of docs and blog assets and when you have external people who aren't developers themselves come and upload these images, optimizing them, compressing them, making sure they're the correct size and defining like the width and height just don't come naturally.
Charlie: And I think also the tooling has gotten better around this even if you're not necessarily a developer using a build process like ImageOptim is a really great free app on Mac. I'm not sure if it's on other systems, but it makes it really easy to drag, drop in a lossless way and has a really incredible impact on asset sizes.
Ben: Yeah. If it's like tiny PNG in the past, run it through there, if you don't want to download like another tool on your computer per se and even optimizing SVGs you can run it through I think it's SVG-go, and then that just goes ahead and compress the SVGs down so that they're a bit smaller.
Charlie: So where there some other maybe less well known optimizations that a developer could use to improve performance?
Ben: So a few less phone optimizations. A lot of them have bucketed under limiting the blocking requests. So in order to understand what I kind of mean by that, you should like to find the term what a blocking request is. So when the browser actually starts making requests to download assets, not every asset is equal, which is kind of terrible in a sense. But in order to understand why every asset is not equal, it's because each of them is needed in a different part of the process.
Ben: All of your CSS is a blocking request. And blocking request are assets, which the browser directly needs in order to render something on the screen. So it blocks that process until it has that resource, which is why a CSS file is blocking. It can construct that CSS object model without all the CSS.
Ben: And that's purely because in order to construct that CSS object model, it will need every single CSS file you have because it needs to know all of the selectors and of course it can't get all of the selectors, unless it has the file. So one little trick when it comes to optimizing your website is to start having non blocking CSS file media queries. So you can start splitting out your CSS and to separate bundles based on whether it's for your mobile view or tablet view. Your desktop print for people who still do print style sheets these days and you can create these link tags. You can then say myprint.CSS file, give it that little media attribute and say it's for a screen when it's between the sizes or larger than this or smaller than that. And then that gives a little like helpful hint to the browser saying, "You know, you're not going to actually need this file unless you meet this criteria, therefore don't download it. Well only download it when you need to."
Charlie: Sure. Yeah, I hadn't thought about using that for display sizes as well. That should be interesting.
Ben: Yeah, it gives that little hopeful hint and so instead of splitting them out into having this one monolithic CSS, you can just haves a couple of smaller ones, which can then start downloading. So of one of those ones, which don't quite know it's there are useful until it actually comes to doing it.
Charlie: Yeah, Sure.
Ben: Till someone points it out maybe.
Charlie: Yeah, true, or listens to this podcast.
Ben: It will then download that with a lower priority just whenever it has time, that can be before it's finished passing the document. That could be after or, marking them as the third as useful as well. It will then keep that order that it sees them in for all the third assets, that is, and will be executed after the DOM has been constructed after that HTML has been passed.
Charlie: Right. Interesting. So I think it's like saying load it whenever you want and defer is like saying wait until the DOM is fully loaded and then run through these in this order.
Ben: Yeah, that's correct. And so it's really important to remember that your script does have external dependencies use deferred. If it doesn't use asynchronous, I've fallen into a habit.
Ben: I think the first time I actually played around for this pair, I marked a couple of them as asynchronous expecting that they'd still be right, run, executed in that order. Actually I ended up like shipping out of a bug where it didn't because the file initially there wasn't downloaded. The second one was because it was cached and you get weird out of order.
Charlie: Yeah, you're going to get some quirky bugs, right? Which are always the hardest to solve. Right?
Ben: Finally you load up their page and then it starts working for you and you don't understand, "Why it is working for me but not them?" So another useful optimization is just preloading or prefetching assets that you have. One that's particularly useful are fonts. If you have custom fonts on your website, you can just add a link tag at the top and say you're going to need this font Comic Sans or whatever. It can then add that at the top and it will then like start fetching it.
Ben: Instead, I can just tell Charlie right now, it's just a little bug that little... Maybe. Or then if you don't want to like provide that little hint that by the way, you're going to need this font because the reason why I particularly mentioned fonts, is in order for a browser to know that it needs to download a font, it needs to download that CSS file giving it that little extra hint just avoids another long request where it downloads a CSS file. It's been waiting for that CSS file, then reads through it, it's like, "Hang on, I need this font now. Now I've got to go away and fetch this font." Just like extra layer that it can avoid-
Charlie: Yeah, sure.
Ben: ... and start downloading, the font while it's downloading that CSS script.
Charlie: Yeah, that's a great point actually especially, for single page apps like you really without the bundle not a lot's going to happen. So that makes sense to prefetch that as soon as possible.
Charlie: Yeah, I know.
Ben: And then another one on the topic of limiting blocking requests. This isn't quite a blocking request most of the time, but sometimes it can help combining your images, like you can use SVG symbols now kind of comes from that old school like spreadsheeting, making spreadsheets.
Charlie: Actually Heroku's product in marketing icons use a lazy-loaded SVG spreadsheet, basically a set of definitions and then the use attribute in SVG to then grab them out of that.
Ben: Yeah, and I think we've done the same at Raygun. I remember on doing a bit of work on our public site where we combined all of the different icons we had into a single SVG. It cut down number of requests from 64 to it was like 15 or little something just from combining all of those icons and their own individual bests.
Charlie: Yeah, sure. So always just trying to only serve the stuff that you actually need for the user.
Ben: Yeah, exactly.
Charlie: Well, I can put a link to the code splitting in the show notes again.
Ben: And there are a lot of tools that can help automate this process for you. It's really a big thing I know Reactor to started to do and probably other frameworks have started looking into as well, webpack provides useful mechanisms for leveraging this where it will just split up your files into multiple chunks as they're called and then you can start using in React Land. It's called like Lazy and Suspense to then if a component is rendered and it will download that asset and it will then include it and it will then start actually using it after it's rendered on the screen.
Ben: And so that's how I know at Raygun, we render our graphs, which is where that little helpful tip comes in. We only download the graph file, the actual vendor script, which accounts for a good chunk of our bundle when it comes to rendering our graph in that way not all users are hit by that performance.
Charlie: I know there's tools out there to check the size of that and I know that Webpacker has some really good tools as well actually on measuring your bundle and seeing what each part is made up of and where that potential bloat is coming from.
Ben: Yeah, some of those visualization tools can really come in handy when it comes to trying to tune them, especially when you can start seeing in relation to one another, this is how long this chunk is taking. So how can I start to look at optimizing this? And that's where our tree shaking can come in handy as well. When you start looking at methods to just eliminate the data code that is not used anymore.
Charlie: Sure. So if you've loaded in a library and you've used one aspect of it, tree shaking will look for ways to, I don't know for a better term of it than shake that bit of code out of the tree.
Charlie: So there's a ton of stuff there that you can do and then you know I guess in like the feedback loop, to tune your performance, you kind of come back and you recheck these aspects of, of your critical rendering path and recheck these pieces of your code or your asset bundles or any of those things. But at what point do you think maybe that's enough now, maybe we've optimized? Because I'm guessing that this is something that you could just keep going and squeezing and going and squeezing forever.
Ben: Yeah. It is one of those iterations where you can go through a stage where you start looking at optimizing, you do a whole lot of optimization and then over time it degrades and as you build new features that can just... Their critical render goes up and up and up again, and that's when you start to, Oh I now need to do another round and reoptimize. And I think this is where we need to start thinking about performance is more of a... It is that constant iteration, that's something we should be checking more frequently. And there are some cool ideas floating around now where I'm sitting, performance budgets.
Charlie: Oh, interesting.
Ben: This is where you can measure your time to first paint your load time. You choose a metric. You start measuring that over a period of say two weeks. You then fit your performance budget to be at the, maybe not the peak of that, but around about the longest time it took or what that trend is and then as you make and deliver new features, you come and check back after another two weeks, and if you have made any improvements. You then bring that budget back down and you're not allowed that actual line that you've set to go up higher.
Ben: If it does go higher, that's when you need to do a round of iterations to improve it. But if it goes lower, you start like dragging that line back down. You start saying, "hang on, like we need to improve this, have it keep getting better, or at least stay the same over time."
Charlie: So iterative optimizations is better than coming out at once every six months and being like, "Oh gosh, we have so much to do."
Ben: Yeah. And then you've got to spend all that extra time like just trying to crawl back. Yeah.
Charlie: Yeah, I think that's a really interesting balancing act as well and finding a way to formalize that process, whether you're in a small business or big business or a freelancer like all time is relative, so I think it's like finding a process that you can stick to and that feels maintainable and feels like it becomes part of your process, is probably a valuable way to kind of keep on top of optimizing overall.
Ben: Yeah, I'd say that as well. It's something that should just starts to come natural. You start to make the deployments and you're like, "Oh, how's the performance looking? Heavy?" Much like how you make a deployment. You should be seeing how many errors are there, is it working or not? Start having a look at their performance and be like, "Oh yeah, the performance is looking great," Or, "Oh no. What did we do?" At Raygun we have a previous monitoring tool as well, which you can plug into and start to track flow time and other metrics.
Charlie: Oh, very cool.
Ben: That always when it comes to doing some of these performance for a first time person jumping in and trying to look at how do I improve the performance, the good mentality to have is always measure first and then optimize. And then at the very least if you do that you can then start to look at the percentages you've gained. That'd be I improved the performance by this amount.
Charlie: Everyone loves the number that is going in a better direction. Right? And I think it's something that I like that idea of making it something that you can be excited about or say and be proud of or feel like you've made a difference for all the issues that having a slow load time is going to have on your clients or an accessibility or on the way that people interact with your work. It feels like a really valuable thing and it feels like there's a ton of tools and tricks that you can use to, I don't know make it better. Like I said, make it better.
Ben: There's ways to make it better.
Charlie: Was there anything else that you know that you wanted to leave people to think about or any other resources you think people should check out?
Ben: Yeah, so the last build things that actually come to mind is always keep your user in mind. Sometimes you can't optimize the flow and that's okay. And so think about the user's perceived performance and in that regard, just things like having spinners or loading states so that they at least get some like feedback around it.
Ben: I know some tips for like, you could show some jokes, you could show some tips, you could show hints. Just give them that feedback, just to say, "something's happening in the background, don't fret."
Charlie: So you don't think it's broken and refresh.
Ben: Exactly. And just take an iterative approach and start just tackling the big banks for back issues first. If you know you can optimize it in a particular way and it's going to give you that most performance gain for the amount of time spent, then just go for that. Think about the value versus cost it'll take to implement.
Charlie: Yeah, that's a really good point. Like if you know you've got a super image heavy site, maybe start by looking at image optimizations. I mean after you've measured, if you know you have a ton of marketing scripts and that bloat is increasing and increasing in a way that doesn't feel checked, then maybe try and pare back on that or find ways to load async or... You know, I feel like the hint to kind of measure first and then optimize is great.
Ben: And otherwise just yeah, have fun.
Charlie: That's just good life advice.
Charlie: Well, Ben, thank you so much for taking the time to chat with me. I will put all the things that we talked about in the show notes. If you want to learn more about any of those things, any of those concepts. Is there a way for people to get in touch with you if they wanted to say hi?
Ben: Yeah, so if anyone wants to say hi, you can always send me an email, "benjamin.pnu.harding [at] gmail [dot] com."
Charlie: Well thank you so much again for taking the time. I really appreciate it.
Ben: Thank you Charlie. It's been great.
A podcast brought to you by the developer advocate team at Heroku, exploring code, technology, tools, tips, and the life of the developer.
User Interface / User Experience Lead, Heroku
Charlie is a designer, developer, musician, and creative coding enthusiast. He can usually be found somewhere in London, probably on a bike.
More episodes from Code[ish]
Justin Abrams, Michael Rispoli, and Eric Chen
SEO has become the digital nucleus around which websites are being developed. Far from being simply about search results, SEO work centers around accessibility, site performance, branding, and more. Increasingly, the gap between SEO work and... →
Badri Rajasekar and Rick Newman
The current pandemic has thrust many workplaces into adopting a remote-first attitude which they may not have been prepared for. Serendipitous events, like chance encounters at the water cooler or camaraderie built during lunches, not only... →
JT Wolohan and Greg Nokes
Python is familiar to most developers as a high-level scripting language that's popular in scientific communities. But some of its main benefits include the data processing ecosystem that's been built around it. In particular, the machine... →