Journal
Practical front-end performance tips
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:
<link rel="preload" href="bg-illustration.svg" as="image" media="(min-width: 60em)">
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
Doppler: Type scale with dynamic line-height
line-height on the web is a tricky thing, but this tool offers a clever solution.
It’s relatively easy to set a sensible unit-less default ratio for body text (say 1.5), but that tends to need tweaked and tested for headings (where spacious line-height doesn’t quite work; but tight line-height is nice until the heading wraps, etc).
Even for body text it’s a not a one-size-fits-all where a line-height like 1.5 is appropriate for all fonts.
Then you’ve got different devices to consider. For confined spaces, tighter line-height works better. But this can mean you might want one line-height for narrow viewports and another for wide.
Then, factor in vertical rhythm based on your modular type and spacing scales if you really want to blow your mind.
It can quickly get really complicated!
Doppler is an interesting idea and tool that I saw in CSS-Tricks’ newsletter this morning. It lets you apply line-height using calc() based on one em-relative value (for example 1em) and one rem-relative value (for example 0.25rem).
In effect you’ll get something like:
set line-height to the font-size of the current element plus a quarter of the user’s preferred font-size
The examples look pretty promising and seem to work well across different elements. I think I’ll give it a spin.
Big list of http static server one-liners
Lots of different options for running a local web server. Choose from npx, ruby, python and many more.
I also like Ritwick Dey’s Live Server extension for VS Code.
Accessible Color Generator
There are many colour contrast checking tools but I like this one from Erik Kennedy (of Learn UI Design) a lot. It features an intuitive UI using simple, human language that mirrors the task I’m there to achieve, and it’s great that if your target colour doesn’t have sufficient contrast to meet accessibility guidelines it will intelligently suggest alternatives that do.
I’m sure everyone has their favourite tools; I just find this one really quick to use!
SVG Gobbler
SVG Gobbler is a browser extension that finds the vector content on the page you’re viewing and gives you the option to download, optimize, copy, view the code, or export it as an image.
This is a pretty handy Chrome extension that grabs all the SVGs on a webpage and lets you see them all in a grid.
Progressively enhanced burger menu tutorial by Andy Bell
Here’s a smart and comprehensive tutorial from Andy Bell on how to create a progressively enhanced narrow-screen navigation solution using a custom element. Andy also uses Proxy for “enabled” and “open” state management, ResizeObserver on the custom element’s containing header for a Container Query like solution, and puts some serious effort into accessible focus management.
One thing I found really interesting was that Andy was able to style child elements of the custom element (as opposed to just elements which were present in the original unenhanced markup) from his global CSS. My understanding is that you can’t get styles other than inheritable properties through the Shadow Boundary so this had me scratching my head. I think the explanation is that Andy is not attaching the elements he creates in JavaScript to the Shadow DOM but rather rewriting and re-rendering the element’s innerHTML. This is an interesting approach and solution for getting around web component styling issues. I see elsewhere online that the innerHTML based approach is frowned upon however Andy doesn’t “throw out” the original markup but instead augments it.
Should I use the HTML5 section element and if so, where?
Unlike other HTML5 elements such as header, footer and nav, it’s never been particularly clear to me when is appropriate to use section. This is due in large part to many experts having expressed that it doesn’t quite work as intended.
I like HTMHell’s rule-of-thumb regarding section:
If you’re not sure whether to use a
<section>, it’s probably best to avoid it.
They go on to recommend that it’s much more important to create a sound document outline. That phrase can be confusing because of the related history of the browser document outline algorithm (or lack thereof) but I think what the author means here is to use and nest headings logically because that alone will give you a “document outline” and also helps AT users scan and skip around the page.
Relatedly: don’t let the original intended use of section tempt you to put multiple H1s on a page in the vain hope that browsers and assistive technology will interpret their nesting level to handle hierarchy appropriately. That would rely on on a document outline algorithm but no browser implements document outlining.
One sensible application of section is to provide additional information to screen reader users about the semantic difference between two adjoining content areas, when that distinction is otherwise only being made visually with CSS.
Here’s an example. Smashing Magazine’s blog articles begin with a quick summary, followed by a horizontal line separating the summary from the article proper. But the separator is purely decorative, so if the summary were wrapped in a div then a screen reader user wouldn’t know where it ends and the article begins. However by instead wrapping the summary in <section aria-label="quick summary">:
- our wrapper has the built-in ARIA role of
region. Aregionis a type of generic landmark element, and as a landmark a screen reader user will find it listed in a summary of the page and can navigate to it easily. - by giving it an accessible name (here via
aria-label) it will be announced by a screen reader, with “Quick summary region” before and “Quick summary region end” after.
Update 07/11/22
Adrian Roselli’s twitter thread on section is gold. Here’s what I’ve gleaned from it:
The reason you would use a section element for accessibility purposes is to create a region landmark. If you are using headings properly, in most cases your content is already well-structured and will not require a region landmark. If you do need a section, note that from an accessibility perspective using the section tag alone is meaningless without providing an accessible name. To provide this, ensure your section has a heading and connect the section to that using aria-labelledby.
You can use section without the above measures and it will not harm users. But be aware it’s aiding your developer experience only, because it’s not helping users. And it may also mislead you and others into thinking you are providing semantics and accessibility which in reality you are not.
References:
- Accessibility of the section element, by Scott O’Hara
- Why you should choose HTML5
articleoversection, by Bruce Lawson
Use Eleventy templating to include static code demos
Here’s a great tutorial from Eleventy guru Stephanie Eckles in which she explains how to create a page that displays multiple code demos, similar to SmolCSS.dev.
It’s interesting that Stephanie uses 11ty shortcodes over other 11ty templating options and that she sometimes declares a variable (via Nunjucks’s set) at the top of the page then intentionally reuses it unchanged in repeated shortcode instances… the example being times where you want to illustrate the same HTML code (variable) being styled differently in progressive demos.
I also like the Open in CodePen feature and section on scoped styling.
Reusable code snippets and components in Eleventy
There are (at least) three cunning ways in Eleventy to get “reusable snippet” or “reusable component” functionality.
- Nunjucks’s
include: Great for just including a common, static element in your template, say aheaderorfooterpartial. (Note that while you canseta variable just before yourincludeline to make that variable available to the included partial, it’s not really “passing in” and scoping the variable like a parameter, so it’s best to reserveincludefor simple stuff.) - Nunjucks’s
macro: Takes things up a notch by supporting passing in parameters which are then locally scoped. You could use this to create a simple component. See Trys Mudford’s article Encapsulated 11ty Components. - 11ty Shortcodes and Named Shortcodes: Shortcodes feel to me pretty much like a wrapper for
macro, whilst also supporting the inclusion ofnpmpackages and the use ofasyncfunctions. However with Named Shortcodes, unlikemacroyou can also pass in ablockof content (rather than arguments alone). This would be handy for any component or layout into which you nest lots of varied content (such as a Stack, for example). See 11ty docs on paired shortcodes for more details.
Hat tip to Jérome Coupé who recently tweeted on this topic and prompted me to gather my thoughts too.
I’ve started reading Stories of your life and others by Ted Chiang.