Optimizing for performance with Eleventy

In the interest of over-engineering my personal site I've gone out of my way to optimize it for performance. It started out fairly quick as it's static, built using Eleventy and is hosted with Vercel but, beyond the basic setup, I've taken some additional measures to drive the pagespeed scores to 100across the board.

Limit client side JavaScript

I use a minimal amount of client-side JavaScript for behavior on my site, namely these blocks from my base.liquid template:

const isDarkMode = () => localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches);
if (isDarkMode()) {
  document.documentElement.classList.add('dark')
} else {
  document.documentElement.classList.remove('dark')
}
...
document.getElementById("toggleDarkMode").addEventListener("click", function() {
  if (isDarkMode()) {
    localStorage.theme = 'light'
    document.documentElement.classList.remove('dark')
  } else {
    localStorage.theme = 'dark'
    document.documentElement.classList.add('dark')
  }
});
(function() {
  const pagination = document.getElementById('pagination');
  if (pagination) {
    pagination.addEventListener('change', (event) => {
      const page = parseInt(event.target.value)
      if (page === 1) {
        window.location.href = '/'
      } else {
        window.location.href = `/${
          event.target.value - 1
        }/`
      }
    })
  }
})()

These blocks allow the dark theme to be set based on the user's system preferences or at their discretion, using a button in the navigation. Additionally, the final function enables navigating between pages quickly using the select at the bottom of each blog listing page.

Everything is in line and concise without external script references. The lone external script is used for Vercel's privacy friendly analytics.

Inline and minify CSS

I use Tailwind for CSS styles[1] which is minified at build time:

ELEVENTY_PRODUCTION=true eleventy && NODE_ENV=production npx tailwindcss -i ./tailwind.css -c ./tailwind.config.js -o _site/assets/styles/tailwind.css --minify

The site include's Prism for code syntax highlighting and this is embedded and minified in the <head>of each page at build time:

{% capture css %}
  {% include "../assets/styles/prism.css" %}
{% endcapture %}
<style>
  {{ css | cssmin }}
</style>

This is made possible by leveraging CleanCSS in (you guessed it) .eleventy.js:

const CleanCSS = require('clean-css')
...
// css filters
eleventyConfig.addFilter('cssmin', (code) => new CleanCSS({}).minify(code).styles)

Minify HTML output

Final HTML output for the site is minified when it's built using @sherby/eleventy-plugin-files-minifier which, per the docs:

This plugin allow you to automatically minify files when builting with Eleventy. It currently supports css, html, json, xml, xsl and webmanifest files.

Implementing this in .eleventy.js is straightforward:

const pluginFilesMinifier = require('@sherby/eleventy-plugin-files-minifier')
...
module.exports = function (eleventyConfig) {
  eleventyConfig.addPlugin(pluginFilesMinifier)
  ...
}

Optimize images

Finally (and this is something that took me longer than it should have to do), we optimize images at build time using @11ty/eleventy-image, defining a shortcode in .eleventy.js as follows:

// image shortcode
eleventyConfig.addShortcode('image', async function (src, alt, css, sizes, loading) {
  let metadata = await Image(src, {
    widths: [75, 150, 300, 600],
    formats: ['webp'],
    urlPath: '/assets/img/cache/',
    outputDir: './_site/assets/img/cache/',
  })

  let imageAttributes = {
    class: css,
    alt,
    sizes,
    loading: loading || 'lazy',
    decoding: 'async',
  }

  return Image.generateHTML(metadata, imageAttributes)
})

This is most impactful on my now page which is populated with quite a few images and, when used, looks something like this:

{% image artistImg, artistName, 'rounded-lg', '225px', 'eager' %}

For this page in particular, the images that are rendered above the fold are set to load as eager to mitigate performance impacts related to too much lazy loading. These images are fetched from caches hosted at Bunny.net when the site is built.

All of these boilerplate steps leave us with a quick to load, accessible and resilient site:

Pagespeed scores for coryd.dev/now


  1. It's easy, flexible and helps mitigate my lack of an eye for design by providing safe baselines. ↩︎