Skater Dad Software aka. Mike

Optimizing Page Load Perf with @11ty/eleventy-img

Mike Montoya

Have an 11ty site? Have images? @11ty/eleventy-img makes it easy to speed up your site.

What's the problem?

As an image format, PNGs are great. The clean, crisp images are easy on the eyes. Transparency is great. Modern screenshot tools create them by default.

Unfortunately, PNG doesn't have a great compression ratio, so the images might be larger than necessary.

New formats like WebP and AVIF have been created to solve this problem. According to Can I Use, browser support for WebP is excellent now in 2022 (even in Safari on Mac), so I want to give it a try.

I'd like to still create PNG, but have my website build process automatically create optimized images. Better yet, I'd like to load normal JPG or PNG when the client does not support WebP.

Enter @11ty/eleventy-img.

Measure before optimizing

Loading SkaterDad.dev with the Firefox Network tab open, we see the game preview images are pretty hefty. Apple Spider's PNG is about 550 KB. Santa Skate's is 148 KB. Even the background texture of the pages is 155 KB!

We can do better.

Network tab image downloads. Much Kilobytes
Network tab image downloads. Much Kilobytes.

Implementing Eleventy Image

First, follow the official documentation to install the package.

Now, in your .eleventy.js config file, add the code to create the image shortcodes. This will let you easily use the plugin from your templates or markdown files.

The code below is adapted from the official example.

I had to add the outputDir and urlPath keys to the metadata object to make this work for my site, which outputs everything to a dist directory.

NOTE: Apparently the urlPath must end in a slash. When I left off the final slash, the URL was still defaulting to /img. YMMV.

const Image = require("@11ty/eleventy-img");

async function imageShortcode(src, alt, sizes) {
let metadata = await Image(src, {
widths: [null],
formats: ["webp", "jpeg"],
outputDir: "./dist/images/optimized",
urlPath: "/images/optimized/",
});

let imageAttributes = {
alt,
sizes,
loading: "lazy",
decoding: "async",
};

return Image.generateHTML(metadata, imageAttributes);
}

module.exports = function (eleventyConfig) {
eleventyConfig.addNunjucksAsyncShortcode("image", imageShortcode);
eleventyConfig.addLiquidShortcode("image", imageShortcode);
eleventyConfig.addJavaScriptFunction("image", imageShortcode);
// ......
// the rest of your config
};

Use the image shortcode in Nunjucks & Markdown

My blog uses Nunjucks and Markdown. To get super-meta, this is how I added the first image of this article to the .md file. You could do the same in a .njk file.

<figure>
{% image "./src/site/images/11ty-img/network_before.png", "Network tab image
downloads. Much Kilobytes" %}
<figcaption>Network tab image downloads. Much Kilobytes.</figcaption>
</figure>

Use the image shortcode in a Nunjucks Include

Things get complicated if you are using the shortcode inside of a Nunjucks Include.

On my home page, I iterate through a list of games in the Nunjucks template. Within the loop, I bring in an include which uses the new shortcode. Lucky me, nothing rendered, and there were no error messages in the console. Admittedly, I have not taken the time to properly learn Nunjucks...

Many Googles later, I found the solution in the plugin Github issues.

Instead of iterating with a for block, you need to use the Nunjucks asyncEach block. Why did Nunjucks create more syntax for this instead of just allowing for to do async stuff? Ugh.

-- game-teaser.njk
<li>
{% image game.image, game.name %}
</li>

-- other-file.njk
<ul class="grid xl:grid-cols-2 gap-4">
{% asyncEach game in games %}
{% include 'components/game-teaser.njk' %}
{% endeach %}
</ul>

HTML Output

The shortcode generates a <picture> element with a preferred source of the WebP image, and fallback to a JPG.

For further reading on the <picture> element, check out the docs on MDN.

<picture>
<source
type="image/webp"
srcset="/images/optimized/yyrJj3ScWb-512.webp 512w"
/>

<img
alt="Network tab image downloads. Much Kilobytes"
loading="lazy"
decoding="async"
src="/images/optimized/yyrJj3ScWb-512.jpeg"
width="512"
height="174"
/>

</picture>

Front page results

After applying the image optimizations to the home page background and game feature images, we can check the Network tab again.

We achieved a 75% reduction in image sizes. 🌟 Great success! 🌟

Network tab image downloads after plugin intergation. Less Kilobytes
Network tab image downloads after optimization.
Scenario Kilobytes
Before 933.21 KB
After 233.4 KB
Savings 699.81 KB

Check out my other blog posts and games!

Want the latest updates?  Follow me on Twitter!