Skip to content
Twenty One Media
webAugust 30, 2026

The Photo Strip Serving 10MB of Raw JPEGs

The about page on our own site had a five-photo strip of founder photos. Nice layout. Each photo was a raw JPEG served directly from public/images. The largest file, a full-resolution camera export, was 5.6MB at 5712x4284 pixels.

Nobody needs a 5,712-pixel-wide photo to fill a card that renders at 384 pixels wide. We were sending the full file to every visitor who loaded that page.

Total payload for the strip: roughly 10.6MB of raw JPEGs on every page load.

What next/image Actually Does

Next.js ships with an Image component that handles optimization automatically. It converts to avif or webp based on browser support, resizes to the dimensions the element actually needs, and serves via CDN. None of that works if you skip the component and use a plain <img> tag, which is what we had.

Switching to next/image is a one-line change in the import. The harder part is the sizes attribute.

The sizes attribute tells Next.js how wide the image element actually renders at each breakpoint. Without it, Next.js defaults to 100vw, assumes the image might fill the full viewport, and generates unnecessarily large files. For a five-column grid where each photo occupies roughly 20% of the screen on desktop and 50% on mobile, the correct value is:

sizes="(min-width: 768px) 20vw, 50vw"

With that in place, Next.js generates exactly the sizes that get used. The avif format compresses the same visual quality to a fraction of the JPEG size.

Result: the strip went from 10.6MB to 78KB delivered. Same photos, same layout, same visual quality.

The Dead Array We Found Along the Way

While we were in the code, we found something else. The site.ts content file had a photos array inside the aboutPage.founder object, listing each photo with its source path and alt text. The about page wasn't reading from it. Instead, it was generating photo paths with a [1, 2, 3, 4, 5].map() loop and hardcoding /images/about/isaac-${n}.jpg.

The array in site.ts was dead. Two sources of truth, one of them never consulted.

When the photos array is the actual source of truth, changing the photo order or adding a new one is a one-line edit in one file. When the page drives from a numeric index, you have to know the convention exists and remember to add a new file in exactly the right filename pattern. It works until someone adds a sixth photo and wonders why the strip still shows five.

We fixed it: the page now reads from the array. The index loop is gone.

Why This Comes Up on Client Work

We run into this pattern on client sites regularly. Photos served raw from the file system, <img> tags instead of the framework's image component, sizes left unset or set to 100vw everywhere.

The fix is always the same: use the tool the framework provides, tell it how wide the image actually renders, and let it handle format conversion. It takes thirty minutes and cuts page weight by 90% or more.

The other pattern, the dead data source, shows up whenever a piece of UI gets rebuilt without deleting the old wiring. The original array in site.ts was the right abstraction. The page just stopped using it at some point and nobody noticed because everything still worked. A working page hides a lot.

If your site is serving full-resolution camera exports to mobile visitors, you already know where to look.