11ty image only generating Webp and AVIF locally, failing on Netlify

I have a small 11ty site running on Netlify and using Decap CMS (formerly Netlify CMS). There’s an image field for uploading JPGs, and I’ve used 11ty Image to generate a Webp and an AVIF, then create a picture element.

The relevant parts of .eleventy.js are:

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

module.exports = function(eleventyConfig) {
  eleventyConfig.addShortcode("image", async function(src, alt) {
    if(alt === undefined) {
      // You bet we throw an error on missing alt (alt="" works okay)
      throw new Error(`Missing \`alt\` on myImage from: ${src}`);

    // Set up the image processing
    let metadata = await Image(src, {
      widths: [650],
      formats: ["jpeg", "webp", "avif"],
      urlPath: "/images/",
      outputDir: "./images/",
      sharpJpegOptions: {
        quality: 50

    // Get data for each format
    let jpegData = metadata.jpeg[metadata.jpeg.length - 1];
    let webpData = metadata.webp[metadata.webp.length - 1];
    let avifData = metadata.avif[metadata.avif.length - 1];

    return `<picture>
      <source srcset="${avifData.url}" type="image/avif">
      <source srcset="${webpData.url}" type="image/webp">
      <img src="${jpegData.url}" width="${jpegData.width}" height="${jpegData.height}" alt="${alt}" decoding="async">

And in the template {% image poster, alt %} where poster is an image path and alt is a string.

That works fine locally, but for some reason I can’t fathom it’s not generating the images on Netlify, and I have to do a git pull, run it locally, then git push to deploy.

I did have it as an arrow function i.e. eleventyConfig.addShortcode("image", async (src, alt) => { but changing it to a function() hasn’t changed anything.

There was a service worker caching admin stuff that I’ve fixed to bypass any requests to /admin, and verified that.

For transparency this is a duplicate of a question I asked on Stack Overflow and haven’t had a response yet.

Could you try adding some logs? I’d personally log the src first, and then using fs.existsSync() to check if the src actually exists and go from there.