Optimising images for the web
Optimising an image is three jobs, in this order of importance: resizing it to the size actually displayed, sending it in a modern format, and declaring its dimensions explicitly in the HTML. An image that earns nothing, though, is better not sent at all; that one comes before all three.
- Lesson 5 of 10
- Beginner
- Free, no signup
The same image, from camera file to what reaches the visitor
The waist is where the decisions are made, and if it is done right once, every visitor after that benefits.
What you have
- the camera file or design export, several megabytes
- a width several times where it will be shown
- extra camera data and location coordinates
What reaches the visitor
- a file at the width it is actually displayed
- a modern format, at a quality the eye cannot tell apart
- explicit dimensions in the HTML, so no layout shift
- lazy loading for every image except the first
This shape shows the order of the work, not the size of the saving. That depends on the image itself: a photo with a flat sky compresses far better and a screenshot full of small text far worse.
Last checked: Facts and tool names in this lesson are re-checked against their sources on this date.
Before any tool, ask the image itself
The lightest image is the one that never gets sent. That is not a slogan, it is a working rule that runs before any compression, and running it is simple: look at every image on the page and ask what the reader would lose if it were gone.
Three families of image usually fail that test. First, decorative ones: the photo of smiling people at a conference table, ornamental icons, the banner above an article that only repeats the title. Second, things that are not images and were saved as one: charts, tables, infographics, icons. Third, screenshots that exist only to prove something the text already says.
The second family matters most because it eats the most bytes and has the easiest replacement. A bar chart saved as a PNG is heavy, unreadable on a phone, and its text is read by no search engine. The same chart in HTML and CSS is a few hundred bytes, stays legible at any size, and its text can be selected and translated. Icons are the same story: an inline SVG comes out lighter than a small PNG and takes its colour from the theme.
This is where we hold a hard position and pay for it: the entire front page of this site contains zero img tags. Everything here that looks like an infographic is drawn with HTML and CSS. We accept the constraint too, because none of these shapes can be a photograph of a real product. If you run a shop, the product photo cannot be deleted, and the rest of this lesson is exactly for that.

The question to answer before opening any compression tool
Does this image need to be an image at all?
Draw it with HTML and CSS
- charts, tables and infographics; the text gets read too
- icons; an inline SVG is lighter than a small PNG
- a decorative photo; delete it and nothing is lost
Then send it properly
- at the size it is displayed, not the original size
- in a modern format, at a quality you tested yourself
- with explicit dimensions in the HTML and lazy for anything lower down
The right branch does not delete everything. A product photo, a real picture of finished work and a screenshot of a measurement are all real content and they stay.
The real size, not the size you uploaded
The most common mistake with website images is uploading the camera file or the Photoshop export directly and then displaying it small with CSS. The browser downloads the whole file and then shrinks it. That means the visitor paid for every byte they will never see.
The right work is two steps. First find the size the image is actually displayed at: open the page in a browser, right click the image, inspect it and read the real element width. Second, produce the file at that size, usually at twice it for high density screens. If an image in your theme is always shown at 400 pixels wide, an 800 pixel file is enough and a 3000 pixel file only burns the visitor data allowance.
In WordPress this is fairly automatic: each upload generates several sizes and the theme hands the browser the most suitable one through srcset. There are two places where that chain breaks, and both are common. First, an image dropped into body copy with the address of the original file, which bypasses the whole mechanism. Second, an image whose dimensions exceed every generated size, such as a 4000 pixel photo for which no intermediate size was ever made.
Format and compression, with real numbers
To take this out of the realm of taste, we took a real file from this server and produced it at two widths in three formats. The original is a 1200 by 630 share cover weighing 57.8 kilobytes as it was uploaded. With ImageMagick, quality 78 for JPEG and WebP and quality 55 for AVIF, this is the table:
| Format | At 1200 wide | At 800 wide |
|---|---|---|
| JPEG | 47.2 KB | 23.5 KB |
| WebP | 20.9 KB | 11.7 KB |
| AVIF | 14.6 KB | 8.3 KB |
Two things fall out of this. First, both moves work and neither replaces the other: changing format at the same width roughly halves the weight, and halving the width roughly halves it again. Second, doing both took that 57.8 kilobyte file down to 11.7 kilobytes, about a fifth, with nothing visible to the eye.
One necessary warning: these numbers are for one image. A photo with a flat sky compresses far better and a screenshot full of small text far worse, and in lossy compression small text is the first thing to fall apart. The comparison of the formats themselves and which suits what is opened in the image formats lesson; here only their effect on weight matters.
The command that produced these outputs is one line and works over a folder too:
for f in *.jpg; do convert "$f" -resize '800x>' -strip -quality 78 "${f%.jpg}.webp"; doneThree things in that command are deliberate. The greater-than sign after the width means only shrink images that are larger, never enlarge a smaller one, and without it the command inflates your small files. strip removes extra data such as the camera model and the GPS coordinates, which is both bytes and sometimes information you did not mean to publish. And quality 78 is a starting point rather than a law; try a few values on your own images.
Which image must not be lazy loaded
Lazy loading means the browser does not download an image that is not yet near the viewport. It is switched on with one HTML attribute and it is a large win on a page with dozens of images.
And this is exactly where the most common speed mistake happens: someone installs a plugin that lazy loads everything, including the big image at the top of the page. Now the browser deliberately delays the image that should arrive first, and LCP gets worse rather than better. Google documentation is explicit: do not lazy load images that are likely to be in the viewport when the page loads, especially LCP images.
The rule that follows is simple. Any image the user sees without scrolling is not lazy. Anything below that is. For that top image there is one extra step available: you can tell the browser with a fetch priority attribute that this one matters more than the rest. And combining lazy with high priority does not work; Google documentation says such an image is still delayed while it is off screen and then fetched with high priority, which is not what you wanted.
Checking it is manual and takes a minute: open the page, view the source, and look for the lazy attribute on the first large image. If it is there, you have found it.
Explicit dimensions, and the trap we fell into ourselves
The last rule is the simplest and the most ignored: every image tag needs its own real width and height. Without them the browser does not know how much space to leave, so it lays the text out and pushes everything down when the image arrives. That is the shift counted in CLS.
With those two attributes the browser computes the aspect ratio from them and reserves the space in advance. So even if your CSS makes the image fluid, the reserved space is right and no shift occurs. That is what lets a site have a CLS of zero and still have responsive images.
And now the trap we fell into ourselves, because no tutorial writes it down. Almost every responsive site carries a global rule that makes image height automatic; the main stylesheet of this site carries exactly that rule. For preventing shift this is fine and correct. But it also means something that gets expensive at the wrong moment: if you set the width in CSS somewhere and leave the height to the HTML attribute, that global rule wins and the box collapses to the file own natural ratio. For us it happened on the avatars of the freelancers page: boxes that were meant to be circles came out as ovals, and the worst case was a photo that was really a tall phone screenshot. The right fix is to let the CSS own the shape, giving both width and height itself and setting the crop with object-fit; the HTML attributes are there to reserve space, not to shape a box.
Four things that must be there, and four that must not
Must be there
- the file width close to the width actually displayed
- a modern format at a quality you tested yourself
- explicit width and height on every image tag
- the first image above the fold, with no lazy loading
Must not be there
- the camera file uploaded as is and shrunk with CSS
- a chart or table saved as a picture
- a plugin that lazy loads every image with no exception
- a height left to the HTML attribute that the CSS then wins
The last row of the upper column is the only item no automatic tool can do for you, because only you know which image is seen first.
The fast path, with AI
The usual way is uploading images one by one to an online service and downloading the output. That works for five images and not for two hundred. The fast path involves a mental shift most people do not make: do not ask the model to optimise your images, ask it to write the command that does. The model is not the tool, it is the toolmaker. What comes out is a script that runs over two hundred images the same day and is still useful the next time.
- First find out the truth about the folder rather than guessing. List once how many files you have, which are the largest and what their dimensions are. If it turns out half the folder weight is five files, the whole job is those five and the rest is wasted time.
- Ask the model for the command and set the conditions yourself. The fast cheap class is enough here; writing a shell loop is not judgment work. But even a cheap model invents parameters, so the conditions have to be in the request itself: do not enlarge, do not delete the originals, and say what each flag does. The current pick in each class is kept in our AI reference.
- Before running on the real folder, try it on three files and look at the output with your eyes. A smaller number alone is not success; if small text inside the image has smeared or the edges are mushy, raise the quality and take it again. This is the only step no automatic tool can replace.
- After the conversion, do not forget the part that is left: the addresses inside the site have to point at the new files and every image tag needs its own explicit dimensions. Converting files without updating the addresses means a folder full of light files nobody ever sees.
Copy-ready recipe
Write me a command line that prepares a folder of images for the web. I will run the command myself, so your job is writing it rather than doing it.
My situation:
Operating system: {Linux | Mac | Windows}
Tools I have installed: {ImageMagick | ffmpeg | none, tell me what is needed}
Folder: {path}, about {count} files with extension {jpg | png | both}
The largest width these images are displayed at on the site: {number} pixels
Conditions, all mandatory:
1. An image smaller than that width must not be enlarged.
2. The original files must not be deleted or overwritten; output goes to a separate folder.
3. Extra data such as camera model and location coordinates must be removed.
4. Output file names must be built from the input names, and spaces and non-Latin characters must not break them.
5. If one file is corrupt, the loop must not stop; print its name.
After the command, write these three things:
1. One line next to each flag saying exactly what it does.
2. A trial version that runs on the first three files only.
3. A separate command that prints the before and after sizes side by side after the conversion.
Rules: do not write any flag you are not sure exists in that version of the tool; if you are unsure, say so and give the safe alternative. Do not promise any saving figure, because it depends on the images themselves. If the tool I named is not suitable for this, say so, and do not tell me to install something I do not have unless it is genuinely necessary.
Before you trust the output: The main danger here is invented flags. The model has seen the pattern of these commands and can write a flag that does not exist or that does something else in your version, and the command either errors out or quietly does something other than what you thought. So before running on the real folder, run the trial version on three files and look at the output with your eyes, and if you do not recognise a flag, look it up in the tool own manual. And one step that should never be dropped: a backup copy of the original folder, before any run.
AI in this kind of work
Our position here is the opposite of what you might expect: in image optimisation, the best use of AI is for it not to touch the images at all. What it genuinely does well is writing the command, explaining the flags, and inspecting the site template. What it is weak at is producing or rebuilding the image itself for the web; there the quality is unpredictable and it also leaves a trace, which the next block is about.
Tools that actually help
- Claude Suited to the fast path job here: it writes the batch command, explains the flags and builds the trial version on three files too. Iran is not on Anthropic supported-countries list, so there is no official signup and no Iranian card is accepted.
- Gemini Useful for looking at the page itself: hand it a screenshot and ask which image is seen first and which sit below the fold. That single answer settles which image must not be lazy loaded. Google own page says the Gemini web app runs in over 230 countries and territories, and Iran is not on that list.
- ChatGPT Enough for writing and explaining the command. We make no claim about access from Iran, because the OpenAI supported-countries page, like the rest of that domain, returns 403 to this server.
Where it backfires
Two risks, and the second belongs to this lesson specifically. First, invented flags: the model has seen the pattern of ImageMagick and ffmpeg commands and can write a flag that does not exist or that means something else in your version. On a folder of images that mistake is not reversible if you overwrote the originals, so the backup is the first step, not the last. The second risk sits in a different corner of the same job: if the images you are optimising were themselves generated with AI, something you cannot see may be travelling with them. Google DeepMind has a tool called SynthID that embeds an imperceptible digital watermark into the output of Google image models, and Google itself can detect it later. That means a site publishing masses of generated images is identifiable to the owner of that model, and compression and resizing were not designed to remove it. Our position is simple: let the model write the command, you run it, and you look at the output. If you want the image itself generated by AI, that is a different discussion and it is opened in the lesson on hidden marks in AI images.
Sources: Google DeepMind: SynthID Anthropic: supported countries Google: where Gemini Apps are available
Where this advice stops
This lesson is about sending images, not about making them; colour choice, composition and which photograph is the right one are not this page job. It has three other boundaries. First, the numbers in the table come from one real image and will differ on yours; the only number to trust is the one you measured on your own files. Second, the modern format advice has an exception: if your audience is on very old browsers you have to send a fallback as well, and then you are keeping two files rather than one. Third, our experience comes from mid sized Iranian corporate and shop sites; in galleries with tens of thousands of photographs, image handling is a system rather than a command, and we have no first-hand experience at that scale.
From our own work
The most important thing we learned about explicit dimensions we learned from a bug of our own, and we have seen it in no tutorial. Line 68 of the main stylesheet of this site carries a global rule telling every image and video to take a maximum width of one hundred percent and an automatic height. That rule is correct and it does not break space reservation either, because the browser computes the ratio from the width and height attributes. But it has a second meaning: anywhere the CSS gives only a width and leaves the height to the HTML attribute, that global rule wins and the box collapses to the file own natural ratio. On 28 August 2026 exactly that happened to the avatars on the freelancers page: boxes that should have been circles came out as ovals, and the worst of them was an image that was really a tall phone screenshot. The right fix was not adding more attributes to the HTML either; it was letting the CSS own the shape, so the avatar class gives its own width and height and sets the crop with object-fit. Now what that experience costs us: every time we build an image box with a fixed shape we have to go looking for that same global rule in the CSS, and that is the price a good rule charges for being good.
Real follow-up questions
Should I convert every image to WebP?
For photographs yes, and the win is substantial. But fix the size before the format: in our measurement, halving the width did as much as changing the format. If your files were made at the right size to begin with, WebP is an extra win rather than a rescue.
Should I install an image optimisation plugin or do it myself?
If you publish content regularly, a plugin that does the work at upload time buys you time. But two things no plugin decides for you: which image should not exist at all, and which image must not be lazy loaded. Those two are always yours.
What quality value should I use?
There is no correct universal number, because it depends on the image. For this lesson we tried 78 for JPEG and WebP and the result was good for an ordinary photograph. The right method is to take three values across a few of your own real images and compare them with your eyes, especially on images containing small text.