Improving upon my image processing with Eleventy July 13, 2021 on

This post is part of a multi-part series on how I built my site using . These are all the posts in the series. Check them out if that's your jam.

When I launched v1 of, I produced a solution for optimizing and serving responsive images that worked, but it felt hacky and kludgy to me. I think I've landed on a better solution this time around.

Markup and styling

First, I wanted to nail down some markup and styling that I was really happy with. After some initial false starts, I landed on the approach recommended by CSS-Tricks in Responsive Images in CSS. Since I'm teaching myself webdev during this process, I wanted to make sure my solution was something that I could truly understand and not just copy and paste some code.

<source type="image/webp" srcset="/images/image-375.webp 375w, /images/image-768.webp 768w" sizes="100vw">
<source type="image/jpeg" srcset="/images/image-375.jpeg 375w, /images/image-768.jpeg 768w" sizes="100vw">
<img src="/images/image-375.jpeg" alt="Don't forget the alt text." loading="lazy" decoding="async" width="768" height="432">

This is much cleaner than the old markup IMO! It allows the browser to do the heavy lifting of deciding which image to serve and I don't have to mess around with media queries.

Optimizing the images

I'm once again using eleventy-img to process my images. Trying to use some other image optimization software just seems unnatural with Eleventy! Haha.

Either the documentation has been improved since December of 2020 or I'm understanding things better but I found the setup a lot easier this time around. I used the Do it yourself: <picture> sample code from use this in your templates with two small tweaks. Here's the code I'm using in my .eleventy.js file. I'll explain the tweaks in a moment.

// .eleventy.js

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

async function imageShortcode(src, alt, sizes = "100vw") {
if(alt === undefined) {
throw new Error(`Missing \`alt\` on responsiveimage from: ${src}`);

let metadata = await Image(src, {
widths: [375, 768],
formats: ['webp', 'jpeg'],
urlPath: "/assets/images/",
outputDir: localDir + "/assets/images/"

let lowsrc = metadata.jpeg[0];
let highsrc = metadata.jpeg[1];

return `<picture>
${Object.values(metadata).map(imageFormat => {
return ` <source type="${imageFormat[0].sourceType}" srcset="${ => entry.srcset).join(", ")}" sizes="${sizes}">`;


module.exports = function(eleventyConfig) {
eleventyConfig.addNunjucksAsyncShortcode("image", imageShortcode);

The sample code from the eleventy-img documentation uses the low res width and height in the img section of the markup. No matter what I did, if I used those values the image would never grow to fill 100% of the parent container. I was thinking that maybe I wasn't understanding something but after spending too much time assuming I wasn't understanding it or doing something wrong, I think it's working like it's supposed to and maybe the documentation could use another little tweak. What I ended up doing was adding let highsrc = metadata.jpeg[1]; to the code sample provided by the documentation. Additionally, I used the values of the new variable in width and height. This now serves the lowres version of the image but at the dimensions that fill up the parent container as the fall back.

My main parent container is 768px wide so that's why I'm using that as my "large" image. 375px is the width of an iPhone screen so that's why I landed on that as my lowres image.

If you're using this code on your own site, you'll need to use the appropriate shortcode. By default Eleventy uses Liquid as the templating engine for markdown files. See the default template engine for markdown files section of the documentation to change this to nunjucks, or just use the liquid shortcode.

I'm pretty happy with this solution but I'm open to suggestions and constructive feedback on anything in this write up. If you have any suggestions, let me know!

That's it for now. Thanks for reading!

This post is part of a multi-part series on how I built my site using . These are all the posts in the series. Check them out if that's your jam.