Right, let’s talk about making your images behave. You’ve got this beautiful, massive 6000x4000 pixel photo from your fancy camera, and you need to shoehorn it into a 300x300 pixel spot in your layout. You can’t just slap it in there and hope for the best. The browser will do it, but it’ll look terrible, slow your site to a crawl, and your users on mobile data plans will personally curse your name. We’re better than that. We’re going to tell the browser exactly how to handle this, and we’re going to do it with a few key tools: resize, crop, fit, and fill.

The first thing to burn into your brain is the crucial difference between resizing an image in the browser and serving a pre-resized image. The former is a crime. Don’t do it. If you use a <img> tag’s width and height attributes or CSS to display a 6000px image at 300px, you’re still forcing the user to download the entire multi-megabyte file. It’s like shipping a grand piano when someone ordered a kazoo, and then using a hydraulic press to turn that piano into a kazoo-shaped object on arrival. It’s wasteful, slow, and frankly absurd.

The correct, professional approach is to serve a pre-resized image. This means you (or your build process, or a service) create a physically smaller version of that image file—let’s say 600x600 pixels—and serve that one to the user. It’s faster, it’s lighter, and it’s what everyone expects you to do.

The resize Filter

This is your workhorse. In most modern frameworks (like Astro, Next.js, Eleventy with image plugins), you’ll use a built-in image component. Let’s use Astro’s as an example because it’s delightfully straightforward.

---
import { Image } from "astro:assets";
import myOriginalImage from "../assets/my-huge-image.jpg";
---

<!-- Astro will generate a new image file at 600x400 pixels and serve that -->
<Image src={myOriginalImage} width={600} height={400} alt="A description" />

Why specify both width and height? It’s not just for the resize operation. It tells the browser the aspect ratio of the image before the image even loads, which prevents the page from jumping around as images load—a classic layout shift nightmare. The resize operation here will preserve the original aspect ratio. If my original image was 6000x4000 (a 3:2 ratio), setting a width={600} and height={400} is perfect because it’s the same ratio. The image will be resized perfectly.

Ah, but what if your requested dimensions don’t match the original aspect ratio? This is where the magic (and confusion) really starts.

fit: 'cover' (The “Crop” You’re Probably Thinking Of)

This is the one you’ll use 80% of the time for hero images, avatars, or any time you need an image to fill a container of a specific size, even if it means cutting off the edges. It resizes the image until it completely fills both dimensions of your target box. Whatever parts of the image don’t fit within the box are unceremoniously chopped off.

<Image
  src={myOriginalImage}
  width={300}
  height={300}
  alt="A square crop"
  format="webp"
  fit="cover"
/>

Imagine your image is inside a box. fit: 'cover' makes the image big enough so that the entire box is covered. The downside? You lose part of the image. The key to using this effectively is focal point. You need to ensure the most important part of your image isn’t the part that gets cropped out. Some advanced services let you specify a focal point for the crop, but often you’re at the mercy of the default, which is usually center. Always, always check your cropped images on important content.

fit: 'contain' (The “Fit” Inside The Box)

The polite, non-destructive cousin of cover. It resizes the image to be as large as possible without cropping anything. The entire image is guaranteed to be visible. The catch? You’ll get letterboxing (empty space) on the sides or top and bottom if the aspect ratios don’t match.

<Image
  src={myOriginalImage}
  width={300}
  height={300}
  alt="Fitted inside a square"
  fit="contain"
/>

This is perfect for product galleries, logos, or any situation where showing the entire image without distortion is more important than filling the space. You’re essentially trading empty space for integrity.

fit: 'fill' (The “Just Make It Fit” Button)

This one should come with a warning label. fill will resize your image to fit your exact dimensions without preserving the aspect ratio. It will stretch or squash your image to fit. This is almost never what you want unless you’re dealing with abstract patterns or backgrounds where distortion doesn’t matter. Using this on a photo of a person will make them look like they’re in a funhouse mirror.

<!-- Use with extreme prejudice -->
<Image
  src={myOriginalImage}
  width={300}
  height={600} // A tall, skinny box
  fit="fill" // This will distort the image to fit
  alt="A probably distorted image"
/>

Just don’t. Seriously. It’s rarely the right tool for the job.

Best Practices and Pitfalls

  1. Always Set alt Text: I shouldn’t have to say this, but I will. It’s accessibility 101. Describe the image for screen readers. If it’s decorative, use an empty string alt="".
  2. Choose Your Format: Use the format prop. Prefer webp or avif for massively smaller file sizes. It’s the easiest performance win you’ll ever get.
  3. Mind the sizes Attribute: For responsive images, the sizes attribute is your secret weapon. It tells the browser what size the image will be displayed at different viewports, so it can fetch the optimally pre-resized image. Not using it is like asking the browser to guess, and it will usually guess wrong, fetching an image that’s too large.
  4. Don’t Forget About quality: You can often specify a quality value (e.g., quality={80}). The default is usually good, but sometimes you can dial it down for a smaller file without a noticeable visual difference, especially on larger images.

The goal is to get the right image, at the right size, in the right format, to the right user. It feels like a chore, but nailing this is what separates professional, blisteringly fast websites from the sluggish, amateurish ones. Now go forth and make those images behave.