ADR-030: Image Optimisation Defaults
Status
Section titled “Status”Accepted
Context
Section titled “Context”Astro’s built-in <Image /> component uses Sharp for image processing and supports multiple output formats, layout modes, and responsive srcset generation. The starter ships with specific defaults in astro.config.mjs that are non-obvious to users:
image: { service: { entrypoint: 'astro/assets/services/sharp', config: { limitInputPixels: 268402689, // ~16K x 16K pixels }, }, responsive: { globalStyles: true, layout: 'constrained', }, domains: [], remotePatterns: [],},These choices affect performance, CLS, and how images behave in layouts. Without documentation, users encounter unexpected behaviour when adding large images or changing layouts.
Decision Drivers
Section titled “Decision Drivers”- CLS prevention: Images must have explicit dimensions to prevent layout shift
- Format efficiency: AVIF > WebP > JPEG for compression ratio
- Responsive by default: Images should serve appropriately sized files to all devices
- Security: Remote image domains must be explicitly allowlisted
- Large image support: Some use cases (photography portfolios, high-res artwork) need to process very large source files
Decisions
Section titled “Decisions”Sharp as the Image Service
Section titled “Sharp as the Image Service”Sharp is the only production-ready image processing service for Astro’s static output mode. The alternative (@astrojs/image with Squoosh) is deprecated. Sharp is explicitly configured rather than relying on Astro’s auto-detection to ensure consistent behaviour across environments.
limitInputPixels: 268402689 (~16K × 16K)
Section titled “limitInputPixels: 268402689 (~16K × 16K)”Sharp’s default pixel limit is ~268 megapixels (16,384 × 16,384). This is explicitly set rather than left as a default to:
- Document the limit — users adding very large source images (photography, scanned artwork) will hit this limit and need to know it exists
- Prevent silent failures — Sharp throws an error rather than silently producing a corrupt output when the limit is exceeded
- Allow override — users with legitimate large-image needs can increase this value in
astro.config.mjs
For typical web content (photos up to ~6000×4000px from modern cameras), this limit is never reached.
layout: 'constrained'
Section titled “layout: 'constrained'”Astro’s responsive image layouts:
| Layout | Behaviour | Use case |
|---|---|---|
fixed | Exact pixel dimensions, no scaling | Icons, logos |
constrained | Scales down to fit container, never scales up | Most content images |
full-width | Stretches to fill container width | Hero images, banners |
constrained is the correct default because:
- It prevents images from rendering larger than their intrinsic size (no blurry upscaling)
- It respects container width constraints
- It generates a
srcsetwith multiple sizes for responsive delivery - It sets explicit
widthandheightattributes, preventing CLS
Users who need full-width hero images should pass layout="full-width" explicitly on those components.
globalStyles: true
Section titled “globalStyles: true”This injects a small CSS snippet that applies max-width: 100% and height: auto to all Astro-processed images globally. Without this, images may overflow their containers on narrow viewports.
Why global rather than per-component? The alternative is adding class="w-full h-auto" to every <Image /> usage. Global styles are less error-prone and consistent with how browsers handle <img> elements by default.
Output Formats: single format, AVIF by default
Section titled “Output Formats: single format, AVIF by default”(Amended 2026-08-13 to ship-truth: the founding text described multi-format <picture> output, which the wrapper does not produce.) The Image wrapper emits a single <img> per source: resolveImageFormat defaults to AVIF (~50% smaller than JPEG), honours an explicit format prop, and passes SVGs through unrasterised. There is no <picture>/multi-format fallback — AVIF support is universal in the template’s supported browsers, and one variant keeps the build and the image cache small.
domains: [] and remotePatterns: []
Section titled “domains: [] and remotePatterns: []”Remote image optimisation is disabled by default. Users must explicitly allowlist external image domains. This is a security decision — processing arbitrary remote URLs would allow SSRF-style attacks if the site ever processes user-provided URLs.
To enable remote images from a specific domain:
image: { domains: ['images.unsplash.com'], // or use remotePatterns for wildcard matching}Raw <img> exemptions (amendment 2026-07-05)
Section titled “Raw <img> exemptions (amendment 2026-07-05)”Constitution rule 8 (“images use the Astro Image component”) is enforced with these exemptions, which the codebase already practised without a record:
- The wrapper’s own fallback:
src/components/atoms/Image.astro(and the card molecules that follow the same pattern) renderAstroImageforImageMetadatasources and fall back to a raw<img>for plain-string sources (public/ paths, remote URLs outsidedomains), which the pipeline cannot type or transform. - SVGs that must not be rasterised: the pipeline converts SVG to raster formats,
destroying embedded CSS animation and self-painted backgrounds. Files like the
Pulci Nella state portraits (ADR-054) and the site logo (
public/logo.svgin Header/Footer) use raw<img>deliberately. Each such usage carries an inline comment stating the reason; the Header/Footer logo usages are covered by this amendment.
Anything outside these two cases still halts on rule 8.
Consequences
Section titled “Consequences”Positive
Section titled “Positive”- Images are responsive and CLS-free by default
- AVIF/WebP output reduces image payload significantly
- Explicit pixel limit prevents silent failures with large source files
- Remote image processing is secure by default (opt-in allowlist)
Negative
Section titled “Negative”constrainedlayout may surprise users who expect images to fill their container — they must uselayout="full-width"explicitlyglobalStyles: trueadds a small CSS injection that users cannot easily override per-image- Very large source images (>16K×16K) require increasing
limitInputPixels
Neutral
Section titled “Neutral”- Sharp must be installed as a dependency — it is listed in
dependencies(notdevDependencies) because Astro’s image service requires it at build time in CI environments - Image optimisation only runs during
pnpm run build, not inpnpm run dev(dev serves originals for speed)
Validation
Section titled “Validation”- CLS: All images must have explicit
widthandheight— verified via Lighthouse CLS audit - Format delivery: Network tab must show AVIF or WebP responses in supporting browsers
- No layout overflow: Images must not exceed their container width on any viewport
References
Section titled “References”- Astro Image Documentation
- Sharp Documentation
- Core Web Vitals: CLS
- ADR-020: Page Performance Patterns
- Performance Budgets
Enforcement
Section titled “Enforcement”- Testable consequences:
- TC-1: no raw
<imgappears insrc/outside the two recorded exemptions (the wrapper’s string-src fallback; unrasterisable SVGs carrying an inline justifying comment). - TC-2: every raster asset in source and build output is within the per-image size budget.
- TC-1: no raw
- Checks:
- TC-1 → check
no-raw-img(status: warn) - TC-2 →
images:gatein CI (status: block, pre-existing gate) — see ADR-057
- TC-1 → check
- Not machine-checkable: per-image layout/format choices remain judgment calls.
- Graduation log: (empty at creation; entries added when a check changes status)
Date: 2026-02-18
Participants: Template maintainers
Outcome: Accepted