Skip to main content

Eleventy version 3: Just how fast can a website be?

Eleventy released version 3.0 back in October 2024. I started to play around with updating my site, but there were a lot of dependencies to manage, and converting the whole thing to ESM was proving to be a lot of work. I decided to take the opportunity to gut the site, and build back only that which was worth it, with a renewed focus on speed and accessibility. Throughout the process, I learned that every feature on a site comes at a cost, whether you like it or not. You cannot do something cool with zero computational load, or add an additional customization without sending additional bytes over the wire. The finished product represents what I feel is a good balance; every piece of functionality should be beneficial to you, the reader.

Starting fresh with a new template

I loved using the Bliss Theme and wish to extend another round of gratitude to Łukasz for all his work. However, the project was officially marked as deprecated this week and so it was a good opportunity to replace the bones with something fresh. In picking a theme, the main criteria were that it had to already support eleventy version 3, and it had to be as minimalistic and unopinionated as possible. The eleventy-base-blog template fit those criteria.

The theme comes with RSS functionality, image optimization, and draft capabilities out of the box. Since I was going to use a CDN for images, I removed the image handling so that images were not stored on the web server. I also use a development branch for draft posts through my vercel setup, and so didn’t need draft posts. This enabled me to remove two plugins right out of the gate.

Lastly, I decided that having a fancy display for the feed wasn’t super important. Most people will just copy the feed into their reader and be on their way. I removed this functionality as well (not really sure what performance cost it would have had, but it would be unnecessary code to maintain).

Now that I had the base in place, I could start adding in features.

Icons

All icons were sourced from iconify and are stored directly on the web server as svg files. This means there are no requests that have to go out to an API to fetch the icons except for the copy button on the code blocks. The header and footer icons are all placed in the _includes directory and then can be called via nunjucks syntax. The svg is directly included in the html served.

Custom font

I use Atkinson Hyperlegible as the main font on this site, however using it comes at a cost. Either the font is loaded from a CDN (in this case I would use bunny), or it is loaded from the web server. After testing both, loading from the web server seemed to be faster. It costs 17.14 kb of transferred data, and adds about 73ms to the loading time (from my machine, according to Firefox dev tools). This was the one big feature that I was willing to spend bytes on, and so I decided to keep it. To eliminate layout shift, I used the font-display: optional parameter, which according to css-tricks.com does the following:

Like fallback, this value tells the browser to initially hide the text, then transition to a fallback font until the custom font is available to use. However, this value also allows the browser to determine whether the custom font is even used at all, using the user’s connection speed as a determining factor where slower connections are less likely to receive the custom font.

JavaScript dependent features

I debated whether I wanted to go with a site that didn’t use any client side JavaScript, however decided on three features that were worth having.

Copy button on code blocks

This uses the eleventy-plugin-code-clipboard plugin. First, additional dependency, second, this means a network request out to cdn.jsdeliver.net to fetch the JavaScript to load. This cost 4.52kb in transferred data and 29ms to wait for the request (again, from my machine, Firefox dev tools). One trick here was to only call the function (through initClipboardJS) on posts, not the whole site. This limits the cost to only the pages where it is necessary.

Pagefind (search page)

This uses the pagefind package and is a great way to enable search on a site. It doesn’t make any network requests or load any third party scripts, however it does require the user to have JavaScript enabled to work. Of course, it also adds another dependency. The css could be made to follow catppuccin colours nicely through the following:

:root {
      --pagefind-ui-primary: #CDD6F4;  /* Text */
      --pagefind-ui-text: #CDD6F4;     /* Text */
      --pagefind-ui-background: #1E1E2E;  /* Base */
      --pagefind-ui-border: #45475A;   /* Surface1 */
      --pagefind-ui-tag: #313244;      /* Surface0 */
      --pagefind-ui-font: sans-serif;
}

/* This controls the styling for the words as they are highlighted */
.pagefind-ui mark {
  background-color: #f9e2af !important;
  color: #1e1e2e !important;
  padding: 0 2px !important;
  border-radius: 3px !important;
}

Analytics

The script for plausible analytics is actually quite tiny, and my guess is that a non-negligible percentage of readers use an extension that blocks it (which I am okay with, the user should have the choice!). The script is only 1.49kb. The real price comes in the 61ms of network time to make the request. However, the script doesn’t affect the layout at all, and therefore shouldn’t affect the load time of the page.

With the transition, the main thing I want to keep analytics for is to monitor for 404 errors. Plausible allows me to see which routes are being requested that end up 404ing, so I can go fix them[1].

Metrics and comparison to previous site

The big test would be to see how this new site compares to the old site on a couple of key metrics. Below is a summary for the root page (/). Note that this is using a CDN for the page itself (Cloudflare) so this may further decrease load times compared to other server setups.

Metric Old site New site Source
Total npm dependencies 763 203 Vercel build logs
Total build time 23s 9s Vercel build logs
Page size 107.5kb 39.8kb Pingdom
Load time 504ms 167ms Pingdom
Performance score 98 100 Lighthouse

waterfall chart showing loading times for various requests How much does a 300ms difference make to the average user? Probably not noticeable, but it was really cool to dig into! Also, the waterfall chart above really highlights how big the font is relative to the page, however that ratio shifts a bit for longer blog posts.

Next steps

I think that I can get performance to be even better by exploring minimization. Currently if you view the source for any of the pages, it will be human readable, with a lot of white space.


  1. As part of this process, I cleaned up all of the file names such that they match the title of the post exactly. This should help anyone who goes looking for the source code for a particular post. ↩︎