WebP can shrink the file, but it does not by itself choose how many pixels the browser downloads. If the conversion only changes the format, a 1,200-pixel image will still be 1,200 pixels, even when it appears at half that width on the screen.

This blog found the problem in the automated performance test. On July 19, 2026, the home featured art went from a graphic motif of about 8 KB to an image of approximately 50 KB. In the continuous-integration environment, Largest Contentful Paint (LCP), the time until the largest eligible content element in the visible area is rendered, reached 2,723 ms and passed the ceiling of 2,500 ms.

The fix was not to degrade the art. The generator began producing 1,200- and 800-pixel versions, while the HTML gained srcset and sizes. In a separate local comparison, before and after on the same simulated mobile profile, LCP fell from 2,793 to 2,122 ms. The format stayed WebP; what changed is that the browser began receiving an alternative compatible with the space occupied.

The original case happened with Astro 7.1.1. In the version in which this text was written, the implementation was rechecked with Astro 7.2.2 and Node.js 24.15.0; today the project uses Astro 7.3.5, and the declared minimum version is still Node.js 22.12.0. srcset and sizes, however, are part of HTML and do not depend on Astro to work.

WebP changes the format; it does not choose the size

Converting an image to WebP can shrink the file, but it does not automatically create versions for different screens. If the HTML offers only this resource, the browser has no alternative:

<img
  src="/images/blog/exemplo/card.webp"
  alt=""
  width="1200"
  height="630"
/>

In this teaching example, the CSS can render the image at 360 pixels wide. The download, though, is still the 1,200-pixel file indicated in src.

Format and resolution solve different problems. WebP compresses each candidate. A responsive image creates smaller candidates and states in which context each one can be useful.

The math that guides the choice

When it uses width descriptors (w), the srcset attribute presents the available files and the natural width of each one. The sizes attribute describes the width the image should occupy in the layout, also called the display space.

<img
  src="/images/blog/exemplo/card.webp"
  srcset="
    /images/blog/exemplo/card-800.webp 800w,
    /images/blog/exemplo/card.webp 1200w
  "
  sizes="(min-width: 768px) 720px, calc(100vw - 2rem)"
  alt=""
  width="1200"
  height="630"
/>

The 800w descriptor does not resize the file. It declares that the resource has a natural width of 800 pixels. The value has to match the real file.

With that information, the browser calculates the effective density of each candidate: the file width divided by the width expected in sizes. The choice can also consider screen density, zoom, and network conditions, and the final result depends on the browser’s implementation. That is why srcset offers options; it is not a rigid order to always download the smallest file.

In the July 19 case, the mobile sizes was 100vw. The profile the gate used had a CSS width of 412 pixels and a device-pixel ratio of 1.75. To set the smallest variant, the project took the full window width as a conservative reference: 412 × 1.75, about 721 nominal pixels. A 720-pixel version would sit slightly below that target. The 800-pixel version offered margin and stayed closer than the 1,200-pixel one.

The detail matters because density turns a seemingly small card into a greater resolution need. Measuring only the CSS width ignores that factor.

The 800-pixel variants occupied 54.6% less

This blog’s cover generator uses Sharp to produce card.webp, at 1,200 × 630 pixels, and card-800.webp, at 800 × 420. This abbreviated representation shows only the transformation of the responsive variant:

const card800 = await sharp(variantSource)
  .resize(800, 420, { fit: 'cover', position: 'centre' })
  .webp({ quality: 75, effort: 6 })
  .toBuffer();

Before this post’s images were included, on August 21, 2026, the 17 pairs then published added up to:

The 800-pixel variants occupied 54.6% less
VariantDimensionsTotal in the archive
card.webp1,200 × 630939,640 bytes
card-800.webp800 × 420426,294 bytes

The 800-pixel variants, added together, occupied 54.6% fewer bytes. The difference combines fewer pixels with the variant’s compression setting, which uses quality 75; it is not an isolated resolution experiment. This measures the files available in the repository, not a saving guaranteed on every visit. The network gain happens only when the browser selects the smaller candidate, and compressibility changes with the art.

The same file occupies different spaces

Creating two images was only half of the fix. The home page uses images from the same archive in three kinds of region with different widths: featured, recent-posts grid, and archive.

The same file occupies different spaces
Regionsizes value
Featured(min-width: 1200px) 1072px, (min-width: 768px) calc(100vw - 128px), (min-width: 640px) calc(100vw - 96px), calc(100vw - 60px)
Recent grid(min-width: 1200px) 520px, (min-width: 960px) calc(50vw - 80px), (min-width: 768px) calc(100vw - 128px), (min-width: 640px) calc(100vw - 96px), calc(100vw - 60px)
Archive(min-width: 640px) 208px, min(208px, calc(100vw - 60px))

The srcset can be the same in all three regions. The sizes cannot, because it declares an estimate of the space each component occupies. Reporting 100vw for a card that occupies only a fraction of the window makes the browser overestimate the need and tends to favor a larger file.

In an equivalent, abbreviated form, the featured configuration looked like this:

<img
  src={featuredCardImage}
  srcset={cardSrcset(featuredCardImage)}
  sizes="(min-width: 1200px) 1072px, (min-width: 768px) calc(100vw - 128px), (min-width: 640px) calc(100vw - 96px), calc(100vw - 60px)"
  alt=""
  loading="eager"
  fetchpriority="high"
  decoding="async"
  width="1200"
  height="630"
/>

sizes does not control the visual width; CSS remains responsible for the layout. width and height set the intrinsic ratio and make it possible to reserve space before loading, which helps avoid content shift.

The alternative text is empty in this case because the cover is decorative and the headline appears beside it as real text. An image that communicates information of its own needs a proper description, not an alt="" copied from this example.

The LCP image cannot wait

On the home page, only the featured image receives loading="eager" and fetchpriority="high". Those attributes already existed before the fix in this case; they were not what recovered the metric. They remain because the image is the likely LCP candidate and needs to be discovered early. The covers below it use loading="lazy", because they can wait until they approach the visible area.

Applying lazy loading to the LCP image creates unnecessary delay. Doing the opposite and marking every cover as high priority also hurts the hierarchy: if everything is urgent, the hint stops helping the browser decide.

That separation completes the solution. srcset and sizes take care of the right resource; loading and fetchpriority take care of timing and priority. This blog’s Core Web Vitals budget remains the gate that confirms whether the combination worked.

When to let Astro generate the variants

Astro can automate a good part of this work when the image is in src/ and is imported through astro:assets. The layout property generates srcset and sizes:

---
import { Image } from 'astro:assets';
import capa from '../assets/capa.webp';
---

<Image
  src={capa}
  alt="Description of the visual content"
  layout="constrained"
  width={1200}
  height={630}
/>

The visual resizing still needs Astro’s responsive styles, enabled by image.responsiveStyles: true, or equivalent CSS in the project.

// astro.config.mjs
import { defineConfig } from 'astro/config';

export default defineConfig({
  image: { responsiveStyles: true },
});

This project’s covers already arrive processed in public/images/blog/, which is why the manual flow is necessary. Astro copies files from public/ without transformation and does not create responsive versions of them.

The choice, then, depends on where the image comes from. A file imported from src/ can use Astro’s service. If it stays in this public/ flow, the file produced by an external generator has to arrive with the variants ready and receive the corresponding HTML.

How to verify without trusting the eye

A page can look correct while transferring the wrong file. Verification has to observe the browser’s decision.

  1. Run npm run build and then npm run preview to open the page on a preview server.
  2. Enable the mobile profile before reloading, disable the cache in the Network tab, and do a fresh load.
  3. Inspect the currentSrc property, which shows which candidate was selected.
const imagem = document.querySelector('.home-featured img');

({
  arquivo: imagem?.currentSrc,
  larguraExibida: imagem?.getBoundingClientRect().width,
  larguraNatural: imagem?.naturalWidth,
  densidade: window.devicePixelRatio,
});
  1. Confirm in the Network tab the transferred file and its size. Resizing an already loaded tab can reuse the larger candidate from the cache; that is why the test starts with the chosen profile and a fresh load.
  2. Run the performance measurement on the page that concentrates the most images. In this project that is npm run test:lighthouse, with attention first on the home page.

The expected result is not “800 pixels on any phone.” It is a candidate consistent with the space, the density, and the conditions the browser evaluated, without sacrificing sharpness or transferring 1,200 pixels for lack of an alternative.

The mistakes that keep WebP heavy

  • Offering variants without a faithful sizes. The browser knows the files, but it receives a wrong description of the space.
  • Using a descriptor that does not match the file. 800w has to mean 800 natural pixels.
  • Applying loading="lazy" to the likely LCP. The priority saving turns into visible delay.
  • Marking every image as high priority. The page loses the distinction between the featured item and secondary content.
  • Testing only by resizing a tab that has a cache. The resource already chosen can mask the new configuration.
  • Copying 800 and 1,200 as universal numbers. The candidates have to come from the layout, the relevant densities, and the project’s images.

The LCP measurements in this case came from a fixed laboratory profile, not from a comparison of real users. Another home page, other art, or another device distribution may require three or more widths. If the image composition also has to change on the phone, the problem becomes one of art direction, usually implemented in HTML with <picture>.

WebP can compress each candidate. srcset offers the alternatives. sizes describes the space. Measurement shows whether the browser received enough information to choose well.

Sources