Here's an interesting question. Given an Eleventy site that has dynamic resources of some kind, how could you provide a way to get those resources in one simple zip file? Here's how I solved that problem.

First, I decided that my "dynamic resources" would be a set of cat pictures, because, of course I did. In a new Eleventy site, I created an images directory, and under that, a subdirectory named cats. In this directory, I dropped a random bunch of cat pictures. (I've got a OneDrive folder of about a hundred of them.) As I wanted Eleventy to be aware of them, I created a _data directory file named catpics.js:

const glob = require('glob-promise');

module.exports = async () => {

    return (await glob('./src/images/cats/*.jpg')).map(p => p.replace('./src',''));


Basically, select all the JPGs and return them in an array, while also removing /src from the result so I end up with an array of paths I could use in HTML later. Here's how I used that:

<!DOCTYPE html>
<html lang="en">
    <meta charset="utf-8">
    <title>Zip Demo</title>
    img {
        max-width: 220px;
        max-height: 220px; 


{% for cat in catpics %}
    <img src="{{ cat }}">
{% endfor %}


The final bit was to ensure my images were copied into the source directory. I did that in my .eleventy.js file:


Here's how this looks when rendered:

Gratuitous cat picture is gratuitous

Honestly, I don't really need to show you this picture but I couldn't resist. Alright, so the next part was to generate the zip file. While I had a good idea of how I was going to create the zip, I struggled with what was the "best" or more appropriate place to handle that logic. In the end, I decided to use Eleventy's eleventy.after event. That event fires after a build is complete. Here's how I used it in my .eleventy.js file:

// Stuff removed to keep the code listing short...
const AdmZip = require('adm-zip');
const glob = require('glob-promise');

eleventyConfig.on('eleventy.after', async () => {
        console.log('after build');
        let catpics = await glob('./src/images/cats/*.jpg');
        let zip = new AdmZip();
        catpics.forEach(c => zip.addLocalFile(c));

I make use of adm-zip, a nice JavaScript zip library I've used before. I make a new zip, add each picture, and when done I wrote it out to my output directory as Back in my HTML, I then just added a link to it:

Download them here: <a href=""></a>

That's it. In theory, now you can drop new images in the cat directory, run a build (hopefully that's automated), and the zip file will be updated when the build is done. If you've done something like this, I'd love to see a real-world example, just let me know. If you want the complete source to this demo, you may find it here:

Photo by Tomas Sobek on Unsplash