I made a little update to the site today to fix how browsers were loading files needed by other files. The custom font I use and the random quote in the header loads in slightly faster now because the browser is downloading all the files it needs at the same time.
My head element looked like this:
<head>
<link rel="stylesheet" href="css/main.css" />
<link rel="stylesheet" href="css/font.css" />
<script src="js/quotes.js" defer></script>
</head>
font.css has properties pointing to the font files. quotes.js has a fetch() call which downloads a text file containing the quotes. This resulted in a download chain that looked like this:
- Download
css/main.css - Download
css/font.css- Download
font.woff2
- Download
- Download
js/quotes.js- Wait for page to finish loading
- Download
quotes.txt
When browsers download the HTML file for a page, they parse the head element and look for files in link elements to download. Over HTTP/2-3 these downloads can happen in parallel. Unfortunately, if these files need additional files—such as font.woff2 and quotes.txt—the browser won’t know that until the files requesting them are downloaded, resulting in another trip to the network.
Like carrying groceries, taking multiple trips has costs: a little bit of extra time, and a huge blow to the ego. We want the browser to grab all the grocery bags/files in one trip, because mama didn’t raise no two-trip loser.
Using rel="preload" in the link element, we can suggest to the browser that it needs these files urgently for the current page and to start downloading them along with the other files linked in the head element, which means I can flatten the download chain to look like this:
- Download
font.woff2 - Download
quotes.txt - Download
css/main.css - Download
css/font.css - Download
js/quotes.js
Preloading font files
For font files, we have to set things up in a special way to persuade the browser to actually use the file it preloaded. We use the as="font" attribute to tell the browser to expect a font file. The crossorigin attribute is shorthand for crossorigin="anonymous". That’s a CORS thing and it’s necessary here because Chromium does not follow section 4.8.2 of the current W3C draft CSS Fonts Module Level 4, which permits same-origin/same-scheme preloads without setting crossorigin.
<link
rel="preload"
href="/fonts/recursive/recursive-latin-variable-wghtOnly-normal.woff2"
as="font"
type="font/woff2"
crossorigin />
Trying to preload font files without the crossorigin attribute in Chromium will result in the browser downloading the font file twice, and a warning in the console will appear similar to the following:
A preload for 'http://localhost:1313/fonts/recursive/recursive-latin-variable-wghtOnly-normal.woff2' is found, but is not used because the request credentials mode does not match. Consider taking a look at crossorigin attribute.
Preloading files requested by scripts
When preloading files requested using JavaScript’s fetch() method, we have to explicitly add options detailing the HTTP request method, enable the sending of credentials, and set the mode to no-cors. If these options are not set, Safari/WebKit will throw a CORS error, refuse to use the preloaded file, and the fetch() call will download the file over the network again.
let response = await fetch('/quotes.txt', {
method: 'GET',
credentials: 'include',
mode: 'no-cors'
});
MDN mentions that the link element needs the crossorigin attribute for as="fetch" preloads, but in my testing this attribute results in the file being thrown out and redownloaded in both Safari/WebKit and Chromium; with and without the extra options in the fetch() method. Like most things related to CORS, I have no idea why and have no interest in trying to find out.
<link
rel="preload"
href="/quotes.txt"
as="fetch"
type="text/plain" />
Results of preloading
Measuring the effect of preloading in a consistent manner proved impossible due to the variance in load times on my machine. One way to tell it’s working is to look at the Network tab of your browser’s dev tools and see if the preloaded files were downloaded at the same time as the main ones.
For my site, I found that the most consistent method was visual: how much of a delay did I see before the font file was swapped in, and how soon did the quote show up in the header. On a fast connection without preloading the font files and the quotes file, there was a tiny but noticeable delay before the system font would get swapped for my custom font, and the quote took a bit longer. This was exponentially worse on a simulated 3G connection.
After preloading, the delays are much less noticeable on a fast connection. The font loads so quickly that I don’t see the system font being used, and if I wasn’t looking straight at it I wouldn’t notice the random quote popping in. On a slow 3G connection, page load times are noticeably more consistent and the font swap is less jarring.
With page sizes as small as mine, preloading these files doesn’t make a huge difference and I’m not about to say everyone should look at what they can preload, but I like the difference all the same.