# Vercel image-optimization guide: raise Core Web Vitals with next/image without making the bill spike

> An image-optimization guide faithful to Vercel's official docs. With real code, it explains AVIF/WebP conversion and CDN caching via next/image, configuring remotePatterns/localPatterns, the cache key (q/w/url/Accept) and 31-day retention / minimumCacheTTL, the _next/image and _vercel/image URL formats, the mechanism of transformation billing (MISS/STALE), and how to keep costs down by drawing the line on 'which images to optimize / not optimize.'

- Published: 2026-06-28
- Author: 友田 陽大
- Tags: Vercel, Next.js, パフォーマンス, コスト最適化, フロントエンド, TypeScript, ISR
- URL: https://tomodahinata.com/en/blog/vercel-image-optimization-next-image-cost-performance-guide
- Category: Vercel in production
- Pillar guide: https://tomodahinata.com/en/blog/vercel-production-platform-guide

## Key points

- Vercel's image optimization is the transformation pipeline next/image (and equivalent Astro/Nuxt components) use. It dynamically converts source images to AVIF/WebP, resizes to the device width, and caches on the CDN. It directly improves LCP and reduces Fast Data Transfer.
- The cache key is Project ID, q (quality 1-100), w (width), url (content hash for local), and the Accept header. Local images are cached on the CDN for up to 31 days; remote ones for the larger of the upstream Cache-Control max-age or minimumCacheTTL (default 3600s).
- Billing is transformation (transformation + cache write on MISS/STALE), cache read, Fast Data Transfer, and Edge Request. So 'don't optimize unneeded images,' 'narrow the quality/width variations,' and 'make caching effective' are the core of cost optimization.
- Images not to optimize: icons/thumbnails under 10KB, animations like GIF, SVG, and frequently changing images. Pass these through with unoptimized and don't waste the transformation quota.
- Limit the sources allowed to be optimized with remotePatterns/localPatterns, and for external images include the account ID in the pathname to prevent abuse. A redeploy doesn't invalidate the cache (purge manually/programmatically).

---

Images are the **heaviest and most LCP-affecting** element on a web page. Optimize them and Core Web Vitals rise, improving SEO and bounce rate. On the other hand, misuse them and **the bill spikes from transformation billing.** Reconciling these two is the crux of Vercel image optimization.

This article summarizes the use of `next/image` and cost optimization, faithful to the official specs of [Vercel Image Optimization](https://vercel.com/docs/image-optimization). For the full picture, see the [Vercel production-operations guide](/blog/vercel-production-platform-guide); for cost, [Active CPU optimization](/blog/vercel-cost-active-cpu-pricing-optimization-guide).

---

## The mechanism: dynamic transformation → CDN cache

Use `next/image` (Astro/Nuxt have equivalent components too) and Vercel **dynamically optimizes the source image.**

1. Use a **bare `img` tag** and optimization is **bypassed**, serving the source image as-is.
2. Use the **`Image` component** and it rides Vercel's optimization pipeline — conversion to AVIF/WebP, resizing to the device width, and CDN caching.
3. Cache states:
   - **HIT**: served from cache (no transformation)
   - **MISS**: transform → cache → serve (**transformation + cache write billing**)
   - **STALE**: serve from cache while re-transforming in the background (same as above)

```tsx
// app/page.tsx — next/image の基本
import Image from "next/image";
import hero from "@/public/hero.jpg"; // ローカル画像（ビルド時に解析）

export default function Page() {
  return (
    <Image
      src={hero}
      alt="製品のヒーロー画像"  // a11y：意味のある alt を必ず
      priority                  // LCP 画像は priority で先読み
      sizes="(max-width: 768px) 100vw, 50vw" // レスポンシブの幅指定
    />
  );
}
```

> **An a11y essential**: `alt` isn't decoration. Give meaningful images an `alt` describing the content, and decorative images `alt=""`. It works for both the experience of screen-reader users and image SEO ([web accessibility](/blog/react-nextjs-web-accessibility-wcag22-guide)).

---

## Configuration: narrow permissions with remotePatterns / localPatterns

State in `next.config.ts` which image sources may be optimized. **Not allowing unlimited** is important for both security and cost.

```ts
// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  images: {
    // ローカル（public 配下）
    localPatterns: [{ pathname: "/assets/images/**", search: "" }],

    // リモート（外部ホスト）
    remotePatterns: [
      {
        protocol: "https",
        hostname: "cdn.example.com",
        // 自分が所有しないホストは、乱用防止にアカウントID等を含める
        pathname: "/account123/**",
        search: "",
      },
    ],

    // 変換のバリエーションを絞る＝キャッシュ効率とコストに効く
    formats: ["image/avif", "image/webp"],
    qualities: [75], // 品質を1つに固定するとキャッシュキーが減る
  },
};

export default nextConfig;
```

> **Abuse prevention**: when allowing a `hostname` you don't own, include an account ID, etc., in the `pathname` to limit to **your own source only.** Widening the wildcard too much risks having your billing inflated by third-party transformations.

---

## The cache key and retention

Optimized images are cached on the CDN, and if the **cache key** is the same, the transformation isn't re-run (= not billed). The key is:

- Project ID
- `q` (quality 1–100)
- `w` (width px)
- `url` (a **content hash** for local, the absolute URL for remote)
- `Accept` header (normalized)

Retention:

| Type | Retention |
|---|---|
| Local image | **Up to 31 days** on the CDN |
| Remote image | The **larger** of the upstream `Cache-Control` `max-age` or `minimumCacheTTL` (default **3600s**) |

> **A redeploy doesn't invalidate the cache.** Invalidate local images by "swapping the contents under the same name and redeploying," or by manual/programmatic [purge](https://vercel.com/docs/cdn-cache/purge). Once a remote image is cached, the old version keeps being served until expiry or purge. "I swapped the image but it's still old" is caused by this.

### The transformation URL format

The `Image` component replaces `src` with an optimization URL.

- Next.js: `/_next/image?url={src}&w=3840&q=75`
- Nuxt/Astro, etc.: `/_vercel/image?url={src}&w=3840&q=75`

---

## Cost optimization: don't make the bill spike

Image optimization is a regular "main cause of increased billing." In effective order.

### ① Don't optimize images that shouldn't be optimized

The following **pass through** with `unoptimized` and use no transformation quota.

- **Icons/thumbnails under 10KB** (already small)
- **Animated images like GIF**
- **SVG** (vector; not a transformation target)
- **Frequently changing images** (caching doesn't work and transformations keep running)

```tsx
// 小さいアイコンや SVG は最適化しない
<Image src="/icon.svg" alt="" width={24} height={24} unoptimized />
```

### ② Narrow the variations

The cache key grows with the combination of `q`, `w`, and `Accept`. Fixing `qualities` to one (e.g. 75) and narrowing `sizes`/`deviceSizes` to only the needed widths **reduces the number of unique transformations = billing, and raises the HIT rate.**

### ③ Make caching effective

- For remote images, extend `minimumCacheTTL` to reduce re-transformations (a trade-off with update frequency).
- Transformation results persist across deploys, so don't purge unnecessarily.

### ④ See what's being consumed

In Observability's **Image Optimization Insights**, check the number of transformations, cache read/write, and Fast Data Transfer to identify heavy spots ([observability](/blog/vercel-observability-monitoring-speed-insights-log-drains-guide)).

---

## When to use / not use

| Should optimize | No need to optimize |
|---|---|
| Product photos / hero images (large, high quality) | Icons under 10KB |
| User-uploaded images | Animated GIF |
| Responsive images whose width changes | SVG (vector) |
| Photo portfolios, etc., where the image is the star | Frequently changing images |

> For user-uploaded images, the staple combination is to store with [Vercel Blob](/blog/vercel-storage-blob-edge-config-marketplace-guide) (client upload) and optimize the display with `next/image`'s `remotePatterns`.

---

## Production checklist (image optimization)

- [ ] Optimize photos/hero/uploaded images with **`next/image`**
- [ ] Set **`alt`** by meaning (a11y, image SEO)
- [ ] **`priority`** on the LCP image, **`sizes`** for responsiveness
- [ ] **`unoptimized` for small icons/SVG/GIF/frequently updated**
- [ ] **Limit sources** with `remotePatterns`/`localPatterns`, narrow the pathname for external
- [ ] **Narrow the variations** of `qualities` and width (HIT rate ↑, billing ↓)
- [ ] Understand **cache invalidation** (same-name swap or purge) when replacing images
- [ ] **Monitor the number of transformations and transfer volume** in Observability

---

## Summary

Image optimization is an area that creates "speed (CWV, SEO)" and "cost" **at the same time.**

1. **`next/image`** for AVIF/WebP conversion, responsiveness, CDN caching
2. Understand the cache key (`q`/`w`/`url`/`Accept`) and **retention**
3. Cost is **① don't optimize unneeded images ② narrow the variations ③ make caching effective**
4. Limit sources with `remotePatterns`; a redeploy doesn't clear the cache
5. **Monitor consumption in Observability**, and measure → optimize

I take on, as a project, tuning that raises CWV while keeping image cost down. This cluster's starting point is the [Vercel production-operations guide](/blog/vercel-production-platform-guide).

> This article is based on the [Vercel Image Optimization](https://vercel.com/docs/image-optimization) official documentation (as of June 2026). Specs, billing, and limits are updated, so confirm the latest values officially at production adoption.
