Image Optimization

Automatic responsive images with WebP and JPEG fallbacks, powered by eleventy-img.

Overview

Ink automatically optimizes images at build time using the @11ty/eleventy-img transform plugin. Every <img> tag in your HTML output is converted to a <picture> element with WebP and JPEG sources at multiple widths. No manual conversion, no external services, no runtime overhead.

How It Works

The image transform plugin runs after Eleventy renders your templates. It finds every <img> tag, processes the source image, and replaces it with a responsive <picture> element:

Input (your template):

<img src="/media/services/hero.png" alt="Our services" loading="lazy">

Output (built HTML):

<picture>
  <source type="image/webp"
    srcset="/img/hero-400w.webp 400w, /img/hero-800w.webp 800w, /img/hero-1200w.webp 1200w"
    sizes="100vw">
  <img loading="lazy" decoding="async"
    src="/img/hero-400w.jpeg" alt="Our services"
    width="1200" height="800"
    srcset="/img/hero-400w.jpeg 400w, /img/hero-800w.jpeg 800w, /img/hero-1200w.jpeg 1200w"
    sizes="100vw">
</picture>

Browsers that support WebP get the smaller file. Older browsers fall back to JPEG. The srcset attribute lets the browser choose the right size based on the viewport.

Default Widths

The transform plugin generates images at three widths by default:

Width Use Case
400px Mobile devices, card thumbnails
800px Tablets, medium viewports
1200px Desktop, full-width images

If the source image is smaller than a given width, that size is skipped automatically.

Per-Image Width Override

Use the eleventy:widths attribute on any <img> tag to override the default widths:

<img src="{{ photo }}" alt="{{ title }}"
     eleventy:widths="200,400" sizes="200px">

This is useful for small images like avatars or logos where generating 1200px variants is unnecessary. The eleventy:widths attribute is removed from the final output.

Controlling Sizes

The sizes attribute tells the browser how wide the image will actually be displayed. This helps it pick the correct source from the srcset:

<!-- Full-width hero -->
<img src="/media/hero.jpg" alt="Hero"
     sizes="100vw">

<!-- Card thumbnail -->
<img src="{{ featured_image }}" alt="{{ title }}"
     eleventy:widths="400,800"
     sizes="(max-width: 600px) 100vw, 400px">

<!-- Small profile photo -->
<img src="{{ photo }}" alt="{{ title }}"
     eleventy:widths="200,400" sizes="200px">

Image Paths in Frontmatter

When referencing images from frontmatter, use paths that start with /:

---
title: "Web Design"
featured_image: "/media/services/web-design.jpg"
---

The leading / ensures the image resolves correctly regardless of which page includes it. Paths without a leading / are resolved relative to the source file, which can break when the same image is referenced from multiple pages (e.g., a card on a listing page and the detail page).

The imageUrl Filter

For cases where you need an optimized image URL rather than a <picture> element -- such as CSS background-image or Open Graph meta tags -- use the imageUrl async filter:

<!-- Hero background -->
<section style="background-image: url('{{ featured_image | imageUrl }}')">

<!-- OG meta tag -->
<meta property="og:image" content="{{ site.url }}{{ featured_image | imageUrl }}">

The imageUrl filter processes the image at widths of 800, 1200, and 1920 pixels and returns the URL of the largest JPEG variant.

Supported Formats

The plugin processes these input formats:

  • JPEG / JPG
  • PNG
  • WebP
  • AVIF
  • TIFF

SVGs, ICOs, and external URLs (starting with http) are passed through unchanged.

Placeholder Images

Both starters ship with minimal placeholder images so cards and team pages have visible content out of the box:

media/
├── services/
│   ├── service-placeholder-1.png    (1200×800)
│   └── service-placeholder-2.png    (1200×800)
├── employees/
│   ├── employee-placeholder-1.png   (600×600)
│   └── employee-placeholder-2.png   (600×600)
└── site/
    └── site-placeholder.png         (800×800)

Replace these with your own images when you're ready. The optimization pipeline handles any image you drop into the media/ directory.

Configuration

The image transform plugin is configured in eleventy.config.js. The default configuration works well for most sites, but you can adjust it:

eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
  extensions: "html",
  formats: ["webp", "jpeg"],
  widths: [400, 800, 1200],
  defaultAttributes: {
    loading: "lazy",
    decoding: "async",
  },
  filenameFormat: (id, src, width, format) => {
    const name = path.basename(src, path.extname(src));
    return `${name}-${width}w.${format}`;
  },
});
Option Default Description
formats ["webp", "jpeg"] Output formats. Add "avif" for even smaller files (slower build)
widths [400, 800, 1200] Default widths generated for each image
defaultAttributes lazy + async Applied to every processed <img>

Performance Impact

On a typical Ink site, image optimization delivers significant improvements:

  • File size reduction -- WebP images are 25-35% smaller than equivalent JPEGs
  • Responsive loading -- Mobile devices download smaller images instead of full-size originals
  • Automatic lazy loading -- Images below the fold load on demand
  • No runtime cost -- All processing happens at build time