Why Images Are So Heavy in WordPress
WordPress is generous by default: when you upload an image, it creates 3–5 size variants automatically (thumbnail, medium, medium-large, large, and the original). A 5 MB original generates 5 derivative files, easily totaling 15–20 MB of server storage for a single image. Multiply this across a site with 200 images and you're looking at gigabytes of storage consumed by a homepage.
The core problem is that WordPress applies no compression by default. It generates the size variants at their native quality, which for many cameras and export tools means the maximum JPEG quality level. A camera JPEG at 95% quality is typically 2–4× larger than a visually equivalent image at 80–85% quality, with no perceptible difference on screen.
There's also the "wrong size" problem: many WordPress users upload a 4000×3000 pixel photo for a blog post where the content column is 800 px wide. WordPress generates a "large" variant, often at 1024 px, but the original 4000 px file is also stored and sometimes served. Browsers download the full-resolution image and scale it down in CSS — doing work that should have been done at upload time.
Compress Before Uploading
The most effective optimization happens before the image reaches WordPress. Compressing at this stage improves all the derivative sizes that WordPress generates from your upload.
For photographs, WebP at 80% quality is the best current choice. WordPress has supported WebP natively since version 5.8, and Chrome, Firefox, Edge, and Safari all support WebP. At 80% quality, WebP is typically 25–35% smaller than JPEG at equivalent perceptible quality. This means your uploaded source, and all WordPress-generated variants, are smaller.
If you need broader compatibility with older WordPress plugins or page builders that don't handle WebP correctly, JPEG at 80–85% is the safe fallback. The quality difference from JPEG at 100% to JPEG at 85% is invisible on screen to virtually every viewer; the file size difference is 2–4×.
Always resize to the actual display width before uploading. If your largest display context for the image is 1200 px wide, upload a 1200 px wide image. Uploading a 4000 px original that WordPress will only ever serve at 1200 px wastes upload time, storage, and the processing time WordPress spends generating variants from an oversized source.
Lazy Loading and the loading='lazy' Attribute
WordPress added native lazy loading for images in version 5.5. Every <img> tag generated by WordPress's image functions automatically receives loading="lazy" unless it's the first above-the-fold image on the page.
Lazy loading defers the download of images until they're about to enter the browser's viewport as the user scrolls. On a long blog post with 10 images, a visitor who only reads the first two sections never downloads the other eight. This reduces initial page load time and page weight, particularly on mobile.
The first above-the-fold image is correctly excluded from lazy loading in modern WordPress, because lazy loading the hero image would delay it appearing — the opposite of what you want for LCP performance. If you're using custom image markup outside of WordPress's functions (in a theme or page builder), make sure to set loading="eager" on above-fold images explicitly.
Core Web Vitals and Images: LCP and CLS
Two of the three Core Web Vitals metrics are directly affected by how you handle images. The Largest Contentful Paint (LCP) measures how long the largest visible element takes to render. For most pages, this is either the hero image or the featured image at the top of the post.
To improve LCP for the hero image, use the fetchpriority="high" attribute on the <img> tag to tell the browser to prioritize its download. Alternatively, add a <link rel="preload" as="image" href="hero.webp"> to the <head> — this tells the browser to start downloading the image during the initial resource discovery phase, before the HTML parser reaches the <img> tag.
Cumulative Layout Shift (CLS) is the other image-related metric. It measures how much the page layout jumps as content loads. Images without explicit width and height attributes cause layout shift: the browser reserves no space for the image until it downloads and knows its dimensions, then the page reflows. Always specify width and height on every <img> tag — WordPress does this automatically for images inserted through the media library, but custom HTML, page builders, and ACF fields often omit these attributes.