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
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
<!-- 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>
open your package.json
file and add dev and build scripts:
// package.json
"scripts": {
"dev": "eleventy --serve",
"build": "eleventy"
}
run the project on your browser
open your favorite terminal and run
yarn dev
now open localhost:8080
on your browser and
it should work without any customized eleventy configuration.
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
<!-- index.njk -->
<body>
<img src="/images/0001.jpg" alt="image no 01" />
<img src="/images/0002.jpg" alt="image no 02" />
<!-- ... -->
</body>
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");
}
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.
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.
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
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>`;
});
}
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" %}
-
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: [
...
]
}
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>'
}
-
const source = ...
andconst 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" %}
<!-- ... -->
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
.
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
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"
)}`;
// ...
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}">`;
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>
Add this styles for img tags
<style>
img {
display: block;
width: 100%;
max-width: 100%;
height: auto;
}
</style>
Test images after lazyloading
Voilà 🎉, now we have a nice looking and fast images on your site.
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