How to optimize images on eleventy (11ty)

Mahmoud Ashraf - Sep 1 '20 - - Dev Community

Originally Published on my blog

Building a site that has images requires to optimize them
to avoid any content shifting and deliver a good user experience.

To achieve that you have to compress, resize, and convert formats for your images.

In this article we will take a look for how to automate your images in eleventy
static site generated website using eleventy-img, and sharp.

Create a basic project to start

create a new directory and name it 11ty-img-example or whatever you want,
then run

yarn init -y
Enter fullscreen mode Exit fullscreen mode

you can use npm if you prefer.

now install eleventy, and create index.njk
on the root with basic html markup.

yarn add -D @11ty/eleventy
touch index.njk
Enter fullscreen mode Exit fullscreen mode
<!-- index.njk -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>11ty img example</title>
  </head>
  <body>
    Hello, World!
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

open your package.json file and add dev and build scripts:

// package.json

"scripts": {
  "dev": "eleventy --serve",
  "build": "eleventy"
}
Enter fullscreen mode Exit fullscreen mode

run the project on your browser

open your favorite terminal and run

yarn dev
Enter fullscreen mode Exit fullscreen mode

now open localhost:8080 on your browser and
it should work without any customized eleventy configuration.

screenshot of application inside the browser

Display some images

let's try get some images and place them in images directory.
and inside index.njk try to display theme.

ls images/

518k   0001.jpg
2.6M   0002.jpg
1.7M   0003.jpg
368k   0004.jpg
679k   0005.jpg
556k   0006.jpg
602k   0007.jpg
1.6M   0008.jpg
1.4M   0009.jpg
Enter fullscreen mode Exit fullscreen mode
<!-- index.njk -->
<body>
  <img src="/images/0001.jpg" alt="image no 01" />
  <img src="/images/0002.jpg" alt="image no 02" />
  <!-- ... -->
</body>
Enter fullscreen mode Exit fullscreen mode

open your browser and it shouldn't render any image.
yeah that's right 😀 because eleventy doesn't handle
assets like css, js, or images so we need to configure that
by ourself.

create a .eleventy.js file on the root directory,
then write:

module.exports = (cfg) => {
  cfg.addPassthroughCopy("images");
}
Enter fullscreen mode Exit fullscreen mode

now everything inside images directory
will be copied to the build directory.

Restart your server and go back to your browser
and it should everything work.

screenshot of application inside the browser

Test images performance without optimization

Let's see how images doing before any optimization.

Open network tab inside the devtool and set fast 3G as network simulation.

in my case it took 50s to render all images, and some of these
images have size more than 2mb. so we need to find a way
to make it faster.

gif showing how slow the image rendering

Add eleventy-img plugin

it's the time to use the eleventy-img, this plugin from
eleventy team you can find the repo from here.

Install it in our project.

yarn add -D @11ty/eleventy-img
Enter fullscreen mode Exit fullscreen mode

open .eleventy.js file and remove the line that we wrote before, then add the code below:

// .eleventy.js

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

module.exports = (cfg) => {
  cfg.addNunjucksAsyncShortcode("Image", async (src, alt) => {
    if (!alt) {
      throw new Error(`Missing \`alt\` on myImage from: ${src}`);
    }

    let stats = await Image(src, {
      widths: [25, 320, 640, 960, 1200, 1800, 2400],
      formats: ["jpeg", "webp"],
      urlPath: "/images/",
      outputDir: "./_site/images/",
    });

    let lowestSrc = stats["jpeg"][0];

    const srcset = Object.keys(stats).reduce(
      (acc, format) => ({
        ...acc,
        [format]: stats[format].reduce(
          (_acc, curr) => `${_acc} ${curr.srcset} ,`,
          ""
        ),
      }),
      {}
    );

    const source = `<source type="image/webp" srcset="${srcset["webp"]}" >`;

    const img = `<img
      loading="lazy"
      alt="${alt}"
      src="${lowestSrc.url}"
      sizes='(min-width: 1024px) 1024px, 100vw'
      srcset="${srcset["jpeg"]}"
      width="${lowestSrc.width}"
      height="${lowestSrc.height}">`;

    return `<div class="image-wrapper"><picture> ${source} ${img} </picture></div>`;
  });
}
Enter fullscreen mode Exit fullscreen mode

Let's break down the code and understand how it works

  • addNunjucksAsyncShortcode

eleventy has feature called shortcodes let
you extend your template engine by writing custom functions.

in our case we will have a new shortcode we can use inside our templates by
writing:

{% Image "/images/00.jpg", "this is an alt description" %}
Enter fullscreen mode Exit fullscreen mode
  • stats = new Image(...)

we pass the src url, formats, and various widths to the image plugin.

So we well have multiple sizes, and formats for each image.

  • const srcset = ...

the stats result look like that

stats = {
  jpeg: [
    {
      url: '...',
      src: '...',
      srcset: '...'
    }
  ],
  webp: [
    ...
  ]
}
Enter fullscreen mode Exit fullscreen mode

We need to covert every size srcset to only one srcset string by using reduce function, So
we can inject it in our code.

so the result of variable srcset

srcset = {
  jpeg: '<srcset>'
  webp: '<srcset>'
}
Enter fullscreen mode Exit fullscreen mode
  • const source = ... and const img = ...

Use webp format for source as main image type and jpg as fallback fro img tag.

Now we are good to go by return the whole picture.

Test Images after using eleventy-img

open index.njk and replace all img tags with

<!-- index.njk -->

<!-- ... -->
{% Image "images/0001.jpg", "image no 01" %}
{% Image "images/0002.jpg", "image no 02" %}
{% Image "images/0003.jpg", "image no 03" %}
{% Image "images/0004.jpg", "image no 04" %}
<!-- ... -->
Enter fullscreen mode Exit fullscreen mode

PS: you have to write image paths include the full path from the root of the project to make it works.

Restart your server and go to the browser. and again open network tab.

and Boom 💥 in this time all images loaded on 5s and no image
has size more than 120kb.

gif showing how the result after using eleventy-img plugin

Add lazy-loading and the blurry effect

this is an extra step to avoid content shifting by using
inline base64 image as placeholder for images and use
javascript as fallback for browser that not supported yet
the native lazyloading with vanilla-lazyload.

install sharp package to get the blurry inline base64 image

yarn add -D sharp
Enter fullscreen mode Exit fullscreen mode

on .eleventy.js import sharp package and add this code below:

// .eleventy.js

const sharp = require('sharp');

// ...

const placeholder = await sharp(lowestSrc.outputPath)
  .resize({ fit: sharp.fit.inside })
  .blur()
  .toBuffer();

const base64Placeholder = `data:image/png;base64,${placeholder.toString(
  "base64"
)}`;

// ...
Enter fullscreen mode Exit fullscreen mode

And then replace src, srcset, and resize, to data-src, data-srcset, and data-resize.

And also add src attribute in <img> tag to src="${base64Placeholder}".

The final code after changes:

const source = `<source type="image/webp" data-srcset="${srcset["webp"]}" >`;

const img = `<img
  class="lazy"
  alt="${alt}"
  src="${base64Placeholder}"
  data-src="${lowestSrc.url}"
  data-sizes='(min-width: 1024px) 1024px, 100vw'
  data-srcset="${srcset["jpeg"]}"
  width="${lowestSrc.width}"
  height="${lowestSrc.height}">`;
Enter fullscreen mode Exit fullscreen mode

And as mentioned before in this article that eleventy only handles html
template engines, So we will use script tag of type="module" to use vanilla-lazyload package.

on index.njk before the end of body tag </body> add this script.

<script type="module" async>
  import Lazyload from "https://cdn.skypack.dev/vanilla-lazyload";
  const lazyload = new Lazyload();
</script>
Enter fullscreen mode Exit fullscreen mode

Add this styles for img tags

<style>
img {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Test images after lazyloading

Voilà 🎉, now we have a nice looking and fast images on your site.

gif showing how the result after using vanilla-lazyload and blurry base64

Conclusion

Now You know how to integrate eleventy-img plugin and vanilla-lazyload package
with your eleventy site if you need to learn more about image optimization, I recommend
check this blog by the author of vanilla-lazyload.

You can find the complete example in github

GitHub logo 22mahmoud / eleventy-image-example

this is a complete example for my blog post



. . . . . . . . . . .