Image Gallery
A responsive image grid sourced from the Pexels API, processed at build time with @11ty/eleventy-img.
Live Implementation
Six real photos fetched live from Pexels, each resized to two widths and encoded as WebP and JPEG, laid out in a plain CSS grid:






How It Works
The Responsive Images and Multi-format Images demos both process one fixed local source image. A gallery needs real, varied photos instead, so this demo fetches six curated photos from the Pexels API at build time and runs each one straight through @11ty/eleventy-img:
const photos = await fetchPexelsPhotos("curated", 6);
for (const photo of photos) {
const metadata = await Image(photo.url, {
widths: [400, 800],
formats: ["webp", "jpeg"],
});
// ...generate HTML for each
} @11ty/eleventy-img accepts a remote URL exactly like a local file path — it downloads the source once, caches it, and generates the same resized/re-encoded output either way. Each photo ends up at two widths and two formats, computed once at build time and cached so a second build doesn’t re-download or reprocess anything already fetched today. The grid itself is plain CSS Grid with auto-fill, no JavaScript, no lightbox library.
Folder Structure
src/_11ty/pexels.js ← shared Pexels fetch helper, used by this and Masonry Gallery
src/_data/imageGalleryDemo.js ← fetches photos, processes each with eleventy-img
src/_includes/demo-live/image-gallery.njk ← the grid markup
src/assets/scss/components/_gallery.scss ← grid + masonry styles Important Files
src/_data/imageGalleryDemo.js
import Image from "@11ty/eleventy-img";
import fastGlob from "fast-glob";
import { fetchPexelsPhotos } from "../_11ty/pexels.js";
async function fallbackPhotos() {
const files = await fastGlob("src/assets/images/demos/gallery/*.jpg");
files.sort();
return files.map((file) => ({ url: file, alt: "" }));
}
export default async function () {
let photos;
try {
photos = await fetchPexelsPhotos("curated", 6);
} catch {
photos = await fallbackPhotos();
}
const items = [];
for (const photo of photos) {
const metadata = await Image(photo.url, {
widths: [400, 800],
formats: ["webp", "jpeg"],
outputDir: "public/assets/images/optimized/",
urlPath: "/assets/images/optimized/",
});
items.push(
Image.generateHTML(metadata, {
alt: photo.alt,
sizes: "(min-width: 40rem) 30vw, 45vw",
loading: "lazy",
decoding: "async",
})
);
}
return items;
} Notes
- This needs a
PEXELS_API_KEYenvironment variable (a free key from pexels.com/api) — set it in.envlocally and as a Netlify build environment variable for the deployed site. See.env.example. - Without a key, this quietly falls back to a handful of generated placeholder images instead of failing the build — a missing API key shouldn’t be able to break every page that happens to show a gallery.
loading="lazy"is on every image here since the grid always renders below the fold on this page. On a real gallery page, the first row or two visible on load should usually drop that attribute.