Fixing Next.js Image Optimization Gotchas
Cover photo by Taylor Vick on Unsplash
So, you deployed your Next.js app and your Vercel bill is climbing faster than expected. You check the logs. It’s the next/image component.
I was looking at a dashboard the other day and noticed that every single request for a thumbnail was hitting the default image optimization API. The problem was that we weren’t defining sizes. Without it, Next.js defaults to a massive width. My tiny profile images were being optimized as if they were full-page hero shots.
Why does it happen?
If you don’t provide the sizes attribute, the browser downloads the largest possible image defined in your deviceSizes config. It tries to be smart, but it lacks context about your CSS.
// The culprit: no sizes attribute<Image src="/cat.jpg" width={200} height={200} alt="cat" />When I added the sizes prop, the difference was immediate. The browser finally knew which version of the image to request (or at least, which one to ignore).
Can I just use a CDN?
“But why not just bypass the built-in loader and use Cloudinary or Imgix directly?” You’re right. That’s a fair point. If you have high-volume traffic, the built-in optimization might be too expensive or slow because it runs on serverless functions. I admit, if you’re serving millions of images, the custom loader approach is almost always better. But for most, fixing the sizes and loader props covers ninety percent of the pain.
The caching trap
Another thing that bit me was caching. Next.js image optimization is handled at the edge, but if you’re behind a custom proxy like Cloudflare, you might be caching the wrong things. I once spent two hours debugging why my images weren’t updating after a build. The culprit was the Cache-Control header being stripped by a middleware file.
Make sure your headers look like this if you want reliable purging:
async headers() { return [{ source: '/_next/image(.*)', headers: [{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }] }]}(I should mention this is only if you control the caching layer, so check your proxy settings.)
Is it worth the effort?
Yes. Just be careful about what you import. Importing images via import cat from './cat.jpg' works great for static assets, but it falls apart when you pull user-generated content from an S3 bucket. You have to use the remotePatterns configuration in your next.config.js file properly.
If you don’t list your domains, the component just fails silently or returns a generic error. It’s frustrating. Does the automatic optimization actually justify the setup time compared to just using a sharp-based worker on your own server?