Lazy Loading for Below-Fold Images
Eigen automatically adds loading="lazy" and decoding="async" to below-fold <img> tags during the build, improving page load performance without any configuration.
How It Works
During the image optimization step (optimize_and_rewrite_images), every <img> tag is evaluated and assigned loading attributes:
- First qualifying image on each page gets
loading="eager". This preserves Largest Contentful Paint (LCP) performance for the above-fold hero image. - All subsequent images get
loading="lazy" decoding="async", deferring their download until they approach the viewport.
First-Image Detection
An image "qualifies" as a potential above-fold image if it is NOT:
- Decorative (
alt=""orrole="presentation") - A small icon (both
widthandheight< 100px) - A data URI (
src="data:...")
The first qualifying image in document order is treated as eager. This heuristic matches the criteria used by the preload hints module (auto_detect_hero_image), keeping the two systems consistent.
Controlling Eagerness
data-eager attribute
Add data-eager to any <img> to force eager loading regardless of position:
<img src="/assets/promo-banner.jpg" data-eager alt="Promo">
The data-eager attribute is stripped from the output HTML. It does NOT consume the first-image slot -- the actual first qualifying image still gets eager treatment independently.
Explicit loading attribute
If a template author sets loading explicitly, eigen preserves it:
<img src="/assets/photo.jpg" loading="eager" alt="Photo">
Exception: if both data-eager and loading="lazy" are present, data-eager wins and loading="eager" is emitted.
hero_image in frontmatter
---
hero_image: /assets/hero-banner.jpg
---
Any <img> whose src matches the hero_image path is always eager. This also consumes the first-image slot.
Behavior When Optimization Is Disabled
[assets.images]
optimize = false
Images remain as plain <img> tags (no <picture> wrapping), but lazy loading attributes are still applied via a lightweight lol_html pass (apply_lazy_loading).
Fragment Behavior
Fragments (partial HTML loaded via HTMX) get their own independent LazyLoadContext. The first qualifying image in each fragment is treated as eager; subsequent images are lazy. Fragments do not receive a hero_image (it is a page-level concept).
Interaction with Preload Hints
The preload hints module auto-detects the hero image by finding the first <img> without loading="lazy". Since lazy loading runs before hints in the pipeline, and the first qualifying image is marked eager, auto-detection correctly identifies the same image. No changes to the hints module are needed.
Attribute Precedence
data-eagerpresent:loading="eager", stripdata-eager- Explicit
loading(nodata-eager): preserve as-is srcmatcheshero_image:loading="eager"- First qualifying image:
loading="eager", consume counter - All others:
loading="lazy"
For decoding:
- Explicit
decoding: preserve as-is - Eager image: do not set (let browser decide)
- Lazy image:
decoding="async"
Implementation
All changes are in two existing files:
src/assets/html_rewrite.rs:LazyLoadContext,is_qualifying_image,resolve_loading_attrs,apply_lazy_loading, and modifications torewrite_img_to_pictureandbuild_picture_html.src/build/render.rs: passeshero_imagetooptimize_and_rewrite_images.