Skip to main content
Brian Cantoni

Solving my Image Dimension Problem with an Eleventy Transform

When migrating from WordPress to Eleventy, I wanted to automatically add width and height attributes to my images for better performance and layout stability. The official Eleventy image plugin felt overcomplicated for my needs, so I built a simple transform that inspects image files and adds dimensions to HTML img tags during the build process.

Eleventy Image Handling

When I migrated this site from WordPress to Eleventy, the image transform plugin was already included in the starter blog template I cloned. This plugin does a lot including outputting multiple sizes and formats. I think it was doing too much for what I needed. I really struggled trying to bring over my WordPress posts and images in a way that retained the old links. Even referencing a local image from Markdown wasn't working. I decided to pull out the 11ty image transform and manage images myself.

I wanted to start with automatically adding height and width attributes, so this is what I tried:

  1. Image plugin -- too complicated, removed
  2. Inspect the image plugin source to possibly copy just the height/width code -- too complicated
  3. Write my own plugin -- seems doable but probably overkill
  4. Write a transform -- this is starting to look the best path!

Eleventy transforms are run late in the build cycle ("postprocessing") and allow any transformations of the built page content. I had a good outline of how I would approach writing this, so I wrote a short spec for Copilot to help. I should have saved that prompt, but I could not get this code to work even with a bunch of adjustments and iterations I made.

After an overnight break I decided to go up a level and just ask Copilot for what I wanted :) This was the simple prompt, focusing just on the outcome:

write an eleventy transform that will automatically add height and width attributes to any found in the HTML source

This worked and with only minimal adjustments got me exactly what I needed!

Image Dimensions Transform

This is my solution for adding height and width attributes to image tags in the HTML output files. A few quick notes:

  • This transform examines the DOM to find all the images, inspect the image file to find the height and width, then add those attributes to the image element
  • We're only interested in .html output files
  • If the page doesn't have any images, skip it (importantly for transforms: they need to just return the original unmodified content)
  • Also skip any images hosted elsewhere (we're only processing local images)

To use this script, include this snippet in your eleventy.config.js file:

/**
 * Eleventy transform to add width and height to <img> tags
 */
eleventyConfig.addTransform(
  "img-dimensions",
  async function (content, outputPath) {
    if (!outputPath || !outputPath.endsWith(".html")) return content;

    const dom = new JSDOM(content);
    const imgs = dom.window.document.querySelectorAll(
      "img[src]:not([width]):not([height])",
    );

    // If no images, return the original content
    if (imgs.length === 0) return content;

    for (const img of imgs) {
      try {
        let src = img.getAttribute("src");
        if (src.startsWith("http")) continue; // Skip remote images

        // Remove leading slash if present
        let imgPath = src.replace(/^\//, "");
        let filePath = `./public/${imgPath}`;
        let buffer = await promisify(readFile)(filePath);
        let dimensions = imageSize(buffer);

        if (dimensions.width && dimensions.height) {
          img.setAttribute("width", dimensions.width);
          img.setAttribute("height", dimensions.height);
        }
      } catch (e) {
        console.log(
          `Error processing image ${img.getAttribute("src")}: ${e.message}`,
        );
      }
    }

    return dom.serialize();
  },
);

Also add these imports at the top of eleventy.config.js:

import { promisify } from "util";
import { readFile } from "fs";
import { imageSize } from "image-size";
import { JSDOM } from "jsdom";

Finally, install the new library dependencies:

npm install jsdom image-size

Example

Here's an example from my Summarizing YouTube Videos with LLMs post. In the Markdown source file, the image is included with this markup:

![Thumbnail for the Honey Influencer Scam YouTube video](/images/honey-scam-thumbnail.jpg)

After the build process and the image dimensions transform, the HTML has the alt text preserved and the newly calculated width and height attributes added:

<p><img src="/images/honey-scam-thumbnail.jpg" alt="Thumbnail for the Honey Influencer Scam YouTube video" width="300" height="180"></p>

Next

One thing I need to look at is the build time. It's not doing any caching, so each build needs to process the 400+ images. Right now my img-dimensions is taking up 23% of the time which isn't horrible but could be better:

[11ty] Benchmark   2668ms  23%   706× (Configuration) "img-dimensions" Transform
[11ty] Benchmark   5770ms  50%   661× (Configuration) "gitLastModified" Nunjucks Filter
[11ty] Copied 453 Wrote 696 files in 11.48 seconds (16.5ms each, v3.0.0)