Images are usually the heaviest part of a page. Choosing the right format can make files much smaller, and a few attributes on img let you control when each image downloads, so the image people see first arrives first.
Choosing a format
| Format | Best for | Notes |
|---|---|---|
| AVIF | Photos and complex images | Usually the smallest files at a given quality; slower to encode |
| WebP | Photos, and graphics that need transparency | Small files, very widely supported |
| JPEG | Photos | Universally supported fallback; no transparency |
| PNG | Screenshots, graphics with sharp edges, transparency | Lossless; large for photos |
| SVG | Logos, icons, simple illustrations | Vector, so sharp at any size; can be styled with CSS |
| GIF | Very small, simple animations | Large and limited to 256 colours; prefer video for animation |
Some practical rules:
- Photos: AVIF or WebP, with JPEG as a fallback if you need one.
- Logos and icons: SVG.
- Screenshots and diagrams with text: PNG or lossless WebP, or SVG if you can draw it as vectors.
- Animations: a short muted
videoin MP4 or WebM, which is typically far smaller than an animated GIF.
AVIF and WebP are supported by all current major browsers. If you need a fallback for older browsers, the picture element lets the browser choose the first format it supports:
<picture>
<source srcset="loaf.avif" type="image/avif">
<source srcset="loaf.webp" type="image/webp">
<img src="loaf.jpg" alt="Sourdough loaf on a wooden board" width="1200" height="800">
</picture>
The img is required. It provides the alt text, the dimensions and the final fallback. The picture element and srcset for different screen sizes are taught in depth in the Responsive Web Design course.
Compression settings matter as much as the format. Export photos at the largest size they will actually be displayed, not straight from the camera, and compare quality settings visually. Tools such as Squoosh let you compare formats side by side.
Lazy loading
By default, the browser downloads every image in the page as soon as it finds it, including images far below the visible area that the visitor may never scroll to. loading="lazy" tells the browser it can wait until the image is close to the viewport:
<img src="gallery-1.webp" alt="Croissants cooling on a rack" width="800" height="600" loading="lazy">
The attribute has two values: lazy and eager. eager is the default, meaning load straight away. Browsers start loading lazy images some distance before they scroll into view, so most people never see a gap.
Browser-level lazy loading for images is Baseline and works in all current major browsers. Browsers that do not support it ignore the attribute and load the image normally, so it is safe to use.
iframe elements support loading="lazy" too, which is useful for embedded maps and videos further down the page.
Do not lazy-load the main image
The most important image on the first screen, often a hero image or a product photo, is usually what the Largest Contentful Paint (LCP) metric measures. Lazy-loading it delays it, because the browser must first work out the layout before deciding to fetch it.
<!-- Wrong: the hero is lazy and loads late -->
<img src="hero.avif" alt="Fresh loaves on the bakery counter" width="1600" height="900" loading="lazy">
<!-- Right: the hero loads straight away -->
<img src="hero.avif" alt="Fresh loaves on the bakery counter" width="1600" height="900">
A simple rule: leave images in the first screenful eager, and lazy-load the rest. Because the visible area varies by device, err on the side of keeping the first few images eager.
Lazy loading needs width and height. Without dimensions, lazy images take up no space until they load, so content jumps as people scroll, and browsers may even load more images than necessary.
fetchpriority
Browsers give images a lower fetch priority than CSS and scripts at first, and only raise the priority of an image once layout shows it is visible. fetchpriority lets you give a hint up front:
<img src="hero.avif" alt="Fresh loaves on the bakery counter" width="1600" height="900" fetchpriority="high">
| Value | Meaning |
|---|---|
high | Fetch this sooner than similar resources |
low | Fetch this later than similar resources |
auto | Let the browser decide (the default) |
Use fetchpriority="high" on one image: the LCP image, such as the hero or main product photo. Marking everything as high priority means nothing is. fetchpriority="low" can help with images in the first screen that matter less, such as the second and later slides of a carousel.
fetchpriority is supported in current versions of the major browsers. Browsers that do not support it ignore it, so it is a safe progressive enhancement. Check its Baseline status on web.dev if you need to know about specific versions.
decoding
After an image downloads, the browser must decode it into pixels. decoding is a hint about whether that work may happen without holding up other content:
async: decode off the main path, so other content can render first.sync: decode together with other content.auto: let the browser decide (the default).
In practice, modern browsers already make sensible choices, and the effect of decoding is usually small. It is fine to leave it out. If you use it, decoding="async" on non-critical images is the common choice.
Putting it together
<!DOCTYPE html>
<html lang="en-GB">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Our bakery</title>
<style>img { max-width: 100%; height: auto; }</style>
</head>
<body>
<main>
<h1>Our bakery</h1>
<img src="hero.avif" alt="Fresh loaves on the bakery counter" width="1600" height="900" fetchpriority="high">
<h2>From the oven this week</h2>
<img src="rye.webp" alt="Seeded rye loaf, sliced" width="800" height="600" loading="lazy" decoding="async">
<img src="buns.webp" alt="Cinnamon buns with white icing" width="800" height="600" loading="lazy" decoding="async">
</main>
</body>
</html>
Check the effect in DevTools: in the Network panel, scroll slowly and watch the lazy images request only as you approach them. The Priority column (enable it by right-clicking the column headers in Chrome) shows the hero as High.
Summary
- Use AVIF or WebP for photos, SVG for logos and icons, PNG for sharp-edged graphics, and video instead of GIF.
- Provide format fallbacks with
picturewhen needed; theimginside is always required. - Add
loading="lazy"to images below the first screen, never to the main image. - Add
fetchpriority="high"to the single most important image. - Keep
widthandheighton every image, especially lazy ones.