I’ve been really interested in the subject of Web Performance since I read Steve Souders’ book High Performance Websites back in 2007. Although some of the principles in that book are still relevant, it’s also fair to say that a lot has changed since then so I decided to pull together some current tips. Disclaimer: This is a living document which I’ll expand over time. Also: I’m a performance enthusiast but not an expert. If I have anything wrong, please let me know.
Inlining CSS and or JavaScript
The first thing to know is that both CSS and JavaScript are (by default) render-blocking, meaning that when a browser encounters a standard .css
or .js
file in the HTML, it waits until that file has finished downloading before rendering anything else.
The second thing to know is that there is a “magic file size” when it comes to HTTP requests. File data is transferred in small chunks of about 14 kb. So if a file is larger than 14 kb, it requires multiple roundtrips.
If you have a lean page and minimal CSS and or JavaScript to the extent that the page in combination with the (minified) CSS/JS content would weigh 14 kb or less after minifying and gzipping, you can achieve better performance by inlining your CSS and or JavaScript into the HTML. This is because there’d be only one request, thereby allowing the browser to get everything it needs to start rendering the page from that single request. So your page is gonna be fast.
If your page including CSS/JS is over 14 kb after minifying and gzipping then you’d be better off not inlining those assets. It’d be better for performance to link to external assets and let them be cached rather than having a bloated HTML file that requires multiple roundtrips and doesn’t get the benefit of static asset caching.
Avoid CSS @import
JavaScript modules in the head
Native JavaScript modules are included on a page using the following:
<script type="module" src="main.js"></script>
Unlike standard <script>
elements, module scripts are deferred (non render-blocking) by default. Rather than placing them before the closing </body>
tag I place them in the <head>
so as to allow the script to be downloaded early and in parallel with the DOM being processed. That way, the JavaScript is already available as soon as the DOM is ready.
Background images
Sometimes developers implement an image as a CSS background image rather than a “content image”, either because they feel it’ll be easier to manipulate that way—a typical example being a responsive hero banner with overlaid text—or simply because it’s decorative rather than meaningful. However it’s worth being aware of how that impacts the way that image loads.
Outgoing requests for images defined in CSS rather than HTML won’t start until the browser has created the Render Tree. The browser must first download and parse the CSS then construct the CSSOM before it knows that “Element X” should be visible and has a background image specified, in order to then decide to download that image. For important images, that might feel too late.
As Harry Roberts explains it’s worth considering whether the need might be served as well or better by a content image, since by comparison that allows the browser to discover and request the image nice and early.
By moving the images to <img> elements… the browser can discover them far sooner—as they become exposed to the browser’s preload scanner—and dispatch their requests before (or in parallel to) CSSOM completion
However if still makes sense to use a background image and performance is important Harry recommends including an accompanying hidden image inline or preloading it in the <head>
via link rel=preload
.
Preload
From MDN’s preload docs, preload allows:
specifying resources that your page will need very soon, which you want to start loading early in the page lifecycle, before browsers' main rendering machinery kicks in. This ensures they are available earlier and are less likely to block the page's render, improving performance.
The benefits are most clearly seen on large and late-discovered resources. For example:
- Resources that are pointed to from inside CSS, like fonts or images.
- Resources that JavaScript can request such as JSON and imported scripts.
- Larger images and videos.
I’ve recently used the following to assist performance of a large CSS background image:
Self-host your assets
Using third-party hosting services for fonts or other assets no longer offers the previously-touted benefit of the asset potentially already being in the user’s browser cache. Cross domain caching has been disabled in all major browsers.
You can still take advantage of the benefits of CDNs for reducing network latency, but preferably as part of your own infrastructure.
Miscellaneous
Critical CSS is often a wasted effort due to CSS not being a bottleneck, so is generally not worth doing.
References
- Inlining literally everything on Go Make Things
- MDN’s guide to native JavaScript modules
- How and when browsers download images by Harry Roberts