Images are the single largest contributor to page weight on most marketing sites. Get them right and your Largest Contentful Paint drops by seconds; get them wrong and no amount of code splitting or caching strategy will save you. This guide covers the formats, the markup patterns, the tooling, and the opinionated calls that actually move numbers in production.
A well-optimized image is invisible. The visitor sees the photo; they don’t experience the file. That gap between what’s served and what’s perceived is where the work lives.
Why image optimization is a Core Web Vitals problem first
Image weight directly determines LCP (Largest Contentful Paint) because the hero image is the LCP element on the vast majority of marketing pages. Google’s field thresholds are clear: LCP under 2.5 seconds is “good.” Every millisecond of image decode, transfer, or render latency chips away at that budget before a single line of JavaScript runs.
CLS is the second casualty when images are handled carelessly. A browser that doesn’t know an image’s dimensions before it loads has to reflow the layout when the image arrives. That reflow is a shift. Those shifts accumulate into a CLS score that pushes you out of the green.
If you haven’t yet worked through the full Core Web Vitals picture, our field guide covers all three metrics and how to prioritize them. Image optimization is the highest-leverage fix for most sites.
Formats: WebP, AVIF, and when to use each
Modern image optimization starts with the right format. JPEG and PNG had a long run; in 2025 they are fallbacks, not defaults.
WebP
WebP has been the pragmatic modern baseline for several years. It offers 25–35% smaller files than JPEG at equivalent quality for photos, and meaningfully smaller files than PNG for illustrations with transparency. Browser support is now effectively universal.
Use WebP as your floor. If you ship nothing else new, switching to WebP from JPEG/PNG alone will cut image payload on most sites by a third.
AVIF
AVIF is the current state of the art. It compresses 40–50% smaller than JPEG and around 20% smaller than WebP at perceptual equivalence. The trade-off has always been encode time: AVIF is slow to generate at build time. That trade-off has largely evaporated as tooling has matured. Both Sharp and Squoosh generate AVIF at workable speeds, and if you’re using a CDN with on-the-fly optimization (more on that below), encode time isn’t your problem at all.
Browser support for AVIF is broad but not universal, which is exactly why <picture> exists.
The <picture> pattern for format negotiation
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="A descriptive alt text" width="1200" height="630" loading="eager">
</picture>
The browser takes the first source it can handle. AVIF-capable browsers get AVIF. Others get WebP. Legacy browsers fall back to JPEG. No JavaScript, no feature detection, no library. Just progressive enhancement baked into the HTML specification.
The right format for each visitor isn’t a build-time decision. It’s a negotiation between server and browser that happens on every request.
Responsive images: srcset and sizes done right
Format is the what; responsive images solve the which resolution. Serving a 2400px hero to a 390px phone screen is a form of waste that no compression algorithm can fully recover.
srcset for resolution switching
<img
src="hero-800.webp"
srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w, hero-2400.webp 2400w"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 80vw, 1200px"
alt="..."
width="1200"
height="630"
>
The sizes attribute tells the browser how wide the image will render before it downloads it. That is the piece most implementations get wrong. Without sizes, the browser guesses 100vw and often fetches a larger variant than necessary.
Write your sizes to match your CSS. If your layout constrains the image to max-width: 900px with padding on either side, say so. The browser will pick the smallest variant that still looks sharp, accounting for device pixel ratio.
Dimension attributes are not optional
Always include width and height on every <img>. Browsers use these to calculate aspect ratio and reserve space before the image loads. Skip them and you get CLS. This is one of those “two minutes of work, meaningful field improvement” fixes that gets skipped far too often.
Lazy loading: defaults and exceptions
The loading="lazy" attribute is now the right default for most images. Browsers implement it natively, defer off-screen images until they’re needed, and handle the logic better than any JavaScript intersection-observer polyfill.
The exception matters: your LCP image must be loading="eager" (or simply omit the attribute, which defaults to eager). Lazy-loading your hero is one of the more common self-inflicted LCP penalties we see in audits.
A practical rule:
- Above-the-fold hero:
loading="eager", and addfetchpriority="high" - Everything below the fold:
loading="lazy" - Images in carousels or tabs that start hidden:
loading="lazy"
fetchpriority="high" on the LCP image tells the browser to push it up the fetch queue, a meaningful LCP improvement on its own, independent of any format or compression work.
Dimension limits and the cost of oversized images
There is no universal “correct” maximum dimension, but some practical ceilings are worth internalizing.
Full-width hero images rarely need to exceed 2400px wide at 2x. A 4K export at 100% quality served to a 1440px monitor is waste, not quality.
Card thumbnails and editorial images at 800px wide are usually sufficient. If you’re feeding a srcset down to 400px on mobile, you don’t need a 3000px source variant.
Dimension × quality × format is the actual file-size equation. A 1600px AVIF at quality 70 will beat a 1200px JPEG at quality 90 on both file size and visual result. Don’t anchor to a dimension; think in terms of the rendered output the visitor actually sees.
Tools worth knowing: Sharp (Node.js, battle-tested in build pipelines), Squoosh (browser-based, useful for one-offs and experimenting with quality settings), ImageMagick (scriptable, runs anywhere). For Astro projects specifically, @astrojs/image wraps Sharp behind a component API that handles srcset generation automatically.
CDNs and on-the-fly optimization
If you’re not using a CDN with image transformation capabilities, you’re doing the work twice: once at build time and again when viewport or format requirements change. Services like Cloudflare Images, Imgix, Cloudinary, and Bunny.net Optimizer accept a source image and return a resized, re-encoded, format-negotiated response based on query parameters or Accept headers.
The pattern is simple: store one canonical high-quality source. The CDN handles everything downstream. Format negotiation via Accept header (the CDN detects AVIF support and serves it automatically), responsive sizing via URL parameters, and aggressive caching at the edge. All without a single build step.
For teams shipping on Astro or similar static-first frameworks, this pairs well. You get clean separation: source assets in storage, delivery logic in the CDN, zero build-time image processing overhead. The cost and performance calculus for this decision connects directly to your rendering strategy. Think about image delivery in the same conversation as how your pages are served.
Serving strategy: what belongs where
A few decisions that trip teams up:
Inline SVG vs. <img> for icons. SVGs loaded via <img> are separate HTTP requests that may block rendering in some configurations. Inline SVG is zero requests and is CSS-animatable. For UI icons, inline is almost always correct.
Background images in CSS vs. <img> in HTML. CSS background images can’t be lazy-loaded natively, don’t support srcset, and are invisible to screen readers. If an image carries visual meaning, it belongs in an <img> with alt text. CSS backgrounds are for decoration.
Preloading the LCP image. For server-rendered pages, adding a <link rel="preload" as="image"> in the <head> for your LCP image pushes it into the browser’s fetch queue before the HTML parser even reaches the <img> tag. Worth doing on landing pages and key conversion pages.
Accessibility is part of the optimization contract
Every <img> gets an alt attribute. Not as a compliance checkbox, but as a commitment to users who navigate by screen reader, have images disabled on metered connections, or are encountering a broken CDN URL. An empty alt="" is correct for decorative images. A meaningful description is correct for everything else.
The case for treating accessibility as a design decision rather than an afterthought applies here too: alt text written at the time the image is chosen is always better than alt text retrofitted six months later.
How Strynal handles image optimization
At Strynal, image optimization is built into the delivery pipeline from day one, not layered on as an audit finding after launch. When we scope and build websites and applications for clients, the image strategy is part of the technical specification: which CDN handles transformation, what the sizes attributes look like for each layout breakpoint, where the LCP image gets preloaded, and how the CMS contributor workflow keeps source assets clean.
Every engagement starts on a blank page. That means we make these calls deliberately for the actual site, not around a template’s assumptions. The result is a site that performs in the field, not just in a lab audit.
If your site is carrying image debt (bloated heroes, missing srcsets, lazy-loaded LCP elements) and you want a senior team to fix it properly, start a conversation with us. We’ll tell you quickly what’s broken and what will actually move the needle.