Vercel Image Optimization transforms your source images into the right format, quality, and size for each device, then caches the result on Vercel’s content delivery network (CDN). Vercel bills you on three meters:
- Image transformations: one per cache MISS and one per STALE revalidation.
- Image cache writes: storing transformed bytes in the global cache, measured in 8 KB units.
- Image cache reads: serving transformed bytes from the global cache, also measured in 8 KB units.
Three things drive those meters: transformation cardinality, transformed file size, and cache time to live (TTL). Each distinct cache key creates its own transformation and cache entry, and a large transformed output can consume several 8 KB read and write units. The number of possible variants depends on the combinations of source images, qualities, widths, and negotiated formats. For example, a single hero image requested in 2 formats and 2 qualities at 8 widths permits up to 32 cache entries. Vercel bills delivery of the optimized bytes separately, as Fast Data Transfer and CDN Requests.
This guide targets a production Next.js 16 App Router app on Vercel. Image Optimization works with other frameworks, but these configuration examples are Next.js-specific. You should aim for two results after deployment: a lower transformation count in Observability and cache reads growing relative to cache writes. A write-heavy ratio means you pay to create transformations that are never reused.
Copy link to headingHow transformations multiply
The Image Optimization API resolves every request through the cache, and what you pay depends on which of three states it returns:
| Cache status | What happens | What you're billed |
|---|---|---|
| HIT | Served from the regional or global cache | Cache read, global cache only |
| MISS | Fetched, transformed, cached, and served | Transformation and cache write |
| STALE | Served stale while revalidating in the background | Transformation and cache write |
Three properties of that cache model drive cost:
- Cardinality: formats, qualities, and widths multiply. Trimming any one dimension proportionally reduces the total.
- Size: Vercel bills cache reads and writes in 8 KB units, so a smaller transformed file costs fewer units and less Fast Data Transfer. Lower quality and narrower widths shrink every output.
- TTL: the cache lifetime of a transformation is whichever is larger, the upstream image's
Cache-Control: s-maxageormax-agevalue, or yourminimumCacheTTLsetting. After the TTL expires, a cached variant is revalidated when it is next requested.
Vercel keys optimized images by project ID, q, w, url (or the content hash for local images), and the normalized Accept header. Vercel negotiates format using Accept, so each extra allowed format adds a cached variant for the browsers that request it.
Remote sources include Amazon S3, a content management system (CMS), and a digital asset manager (DAM). When the upstream cache lifetime is short or absent, minimumCacheTTL determines the minimum interval before a requested variant can require revalidation. The image never has to change for that to happen.
A STALE hit triggers one transformation and cache write per region, not one per request, because Vercel collapses concurrent revalidations. A short TTL on a stable image still re-bills in every region that serves it. A globally distributed audience, therefore, produces one transformation per serving region per cycle for an image that never changes.
Three limits decide what Vercel optimizes at all:
- A transformed image can be at most 10 MB.
- A source image can be at most 8,192 pixels on each side.
- Vercel transforms only
image/jpeg,image/png,image/webp, andimage/avifsources.
Vercel serves every other format, including animated GIF files, unchanged.
Copy link to headingStep 1: Read your current usage
Open Observability > Image Optimization in your team dashboard, then filter to the project you are investigating. Note three things:
- The transformation count for the last 30 days. This is your baseline.
- The ratio of cache writes to cache reads. A healthy project typically reads more than it writes. Writes at or above reads may mean you create transformations that aren’t used, which points to cardinality or TTL issues.
- The
formatsandqualitiesin use. More than one quality value, or a format you did not intend to serve, is an immediate win.
Read those numbers against the billing units. Rates vary by region, so check Regional Pricing for the regions that serve your traffic.
Cache reads have one nuance. If a request recently accessed an optimized image in the same region, Vercel serves it from the in-region cache and incurs no global cache-read unit charge.
Vercel never bills a Hobby team on demand. Once a Hobby team passes the allowance, a new optimization returns HTTP 402 instead of an image. That fires the onError callback and renders the alt text. Images already in the cache continue to work. If only some images break, check whether they are new variants or uncached source URLs.
Copy link to headingStep 2: Fix the multipliers
These changes can reduce billed usage. Work through them in order because the first two act on every image in the project, so start there.
Copy link to headingFix 1: Raise the cache TTL for stable images
In Next.js 16, minimumCacheTTL defaults to 4 hours (14400 seconds), up from 60 seconds in Next.js 15. The effective TTL is the larger of minimumCacheTTL and the upstream Cache-Control age.
When the upstream cache lifetime is short or absent, minimumCacheTTL sets the minimum freshness period. A variant requires revalidation only when requested after its effective TTL expires.
If your images do not change within a month, set the TTL explicitly:
Where you control the upstream, such as your own Amazon S3 bucket, Vercel Blob store, or asset server, set a long Cache-Control: max-age=N there too. For images bundled with your app, use static imports: Next.js hashes the filename and caches the file for a year as immutable.
Use a longer TTL for stable images, and invalidate specific images on demand when they change. To update an image in place, use one of these:
- Purge the cache from the dashboard.
- Run
vercel cache invalidate --srcimg /images/hero.pngfrom the command-line interface (CLI). - Call
invalidateBySrcImage()from@vercel/functions. - Change the
src. A stable content hash or version in the filename is the most reliable option.
Choosing Invalidate in the dashboard, or invalidating a source image via the CLI or SDK, marks existing variants as stale for revalidation on their next request. Changing src creates a new cache key instead.
Copy link to headingFix 2: Serve one format
formats defaults to ['image/webp']. Adding AVIF alongside WebP doubles the transformations and cache entries for every image. Vercel caches an AVIF variant for browsers that support it and a WebP variant for the rest. Keep the default when cost is the priority:
Trade-off: AVIF generally produces smaller files than WebP but takes longer to encode, which can increase latency on a cache miss. A transformation costs the same whatever the format, so the real trade is more cache entries against lower Fast Data Transfer from smaller AVIF bytes. Measure both against your own traffic before you decide.
Copy link to headingFix 3: Allow one quality
Every extra quality value multiplies the variants for the images that use it: two qualities produce twice the cache entries. Next.js 16 defaults qualities to [75] and requires the allowlist.
The allowlist also caps what a per-component quality prop can produce. Next.js 16 coerces a quality prop outside the allowlist to the closest allowed value. For example, quality={80} uses the 75 variant instead of creating a new one. A direct request to /_next/image with a disallowed q returns HTTP 400.
On Next.js 15 and earlier, qualities is opt-in, and an unlisted value triggers an error rather than being coerced to an allowed value. Audit your quality props before you set the allowlist there. To learn more, see the Next.js 16 qualities breaking change and the qualities configuration reference.
Copy link to headingFix 4: Choose widths that match your layout
Next.js generates candidate image URLs in srcset. Browsers choose which candidates to request; listing a width does not automatically create a billed transformation.
Copy link to headingSet project-wide width limits
Use deviceSizes and imageSizes to control which widths Next.js can generate.
| Setting | Next.js 16 defaults | Purpose |
|---|---|---|
deviceSizes | [640, 750, 828, 1080, 1200, 1920, 2048, 3840] | Provides device-width candidates |
imageSizes | [32, 48, 64, 96, 128, 256, 384] | Adds smaller candidates for images using sizes |
Choose a maximum width based on both layout size and supported pixel density:
For example, an image displayed at 960 CSS pixels needs a 1,920px candidate for a 2x display. If that is your project’s largest requirement, you can remove 2,048 and 3,840 from deviceSizes.
Trimming imageSizes removes smaller candidates; it does not lower the default maximum width.
Trade-off: Removing widths can reduce the number of requested variants, but setting the maximum too low can make images look soft on high-density displays. Check your largest layouts and target devices before deploying.
These settings define the available widths across your project. Next, use each image’s dimensions and sizes prop to match those widths to its layout.
Copy link to headingFor fixed-size images, provide dimensions
Use explicit dimensions when an image’s rendered size is fixed:
Without a sizes prop, this generates two candidates with the Next.js 16 defaults:
| Pixel density | Target width | Generated width |
|---|---|---|
| 1x | 1,200px | 1,200px |
| 2x | 2,400px | 3,840px |
Next.js selects the next allowed width that can satisfy the target. Because the defaults have no width between 2,048px and 3,840px, the 2x candidate jumps to 3,840px.
Copy link to headingFor responsive images, describe the layout with sizes
A fill image without sizes defaults to full-viewport sizing and generates eight device-width candidates, up to 3,840px:
If the image occupies only part of the viewport, tell the browser how much space it uses:
This tells the browser to choose a candidate for an image that occupies:
- Half the viewport at viewport widths up to 768px.
- A quarter of the viewport above 768px.
Match these values to your actual layout. The fill image also needs a positioned parent with defined dimensions.
Copy link to headingFix 5: Skip optimization where it cannot help
Some assets may gain little from optimization: logos, icons, images under 10 KB, and animated GIF files. Set unoptimized on those:
Tip: Next.js applies unoptimized automatically when src ends in .svg.
Trade-off: set this per image, not globally. Disabling optimization globally can increase transferred bytes and worsen Largest Contentful Paint (LCP), especially for large photos. The effect on Vercel Fast Data Transfer also depends on where the original images are served.
Copy link to headingFix 6: Allowlist what Vercel can optimize
remotePatterns and localPatterns restrict which URLs the optimization API accepts. In addition to the security benefit, an allowlist can reduce unnecessary optimization requests in two ways:
- It prevents the optimizer from accepting source URLs that don't match your allowed patterns.
- It turns a misbehaving source URL into an HTTP 400, rather than an unbounded stream of new source images. A CMS that appends a unique token per render is the common case.
If query parameters are required, allow the expected query string with search. Where your source supports stable URLs, remove unnecessary variable parameters before passing the URL to Image and preserve parameters required for authorization or selecting the correct image.
Copy link to headingStep 3: Investigate with an agent
This step is optional. If you use a coding agent with access to your repository and the Vercel CLI, the prompt below runs the investigation. Keep the agent read-only until you approve a change.
Investigate why Image Optimization usage is high on this Vercel project. Start read-only: do not edit files, change settings, purge caches, or deploy without my approval. 1. Read next.config.ts or next.config.js and report the effective values of images.formats, images.qualities, images.minimumCacheTTL, images.deviceSizes, images.imageSizes, images.remotePatterns, and images.localPatterns, noting which are defaults for our Next.js version. 2. Grep all Image component usages. Flag quality props with more than one distinct value, fill or responsive images missing a sizes prop, and small static assets such as logos and icons that could set unoptimized. 3. Identify remote image hosts and, where possible, the Cache-Control max-age they return, to estimate revalidation frequency. 4. Check whether any image src URLs include variable query strings or tokens that would create unbounded unique source images. 5. Return each finding, the config or component change that fixes it, the trade-off, and what I should watch in Observability, Image Optimization after deploy to verify it worked.
Give the agent your baseline numbers from Step 1 so it can prioritize. To let the agent pull usage data live, connect the Vercel Model Context Protocol (MCP) server.
Copy link to headingStep 4: Verify after you ship
Config changes take effect on the next deployment, and existing cache entries serve until their cache entries expire, so verification runs over a full cache cycle:
- Deploy the config changes.
- In Observability → Image Optimization, set the date filter to compare the weeks before and after the deploy. The transformation count declines as old entries age out. With a 31-day TTL, the full effect takes a cache cycle, not a day.
- Compare transformation and cache-write usage over periods with similar image-request volume.
- Spot-check visual quality on your heaviest pages at mobile and desktop widths, especially if you removed AVIF or trimmed widths.
- Check Fast Data Transfer over the same period. If it rose after you removed a format or added
unoptimized, re-run the trade-off against your own traffic. Transformation, read, and write rates vary by region, so use the rates for the regions that serve your traffic.
Pro teams should also configure Spend Management to get an alert or to pause projects automatically at a spend threshold. This helps limit the impact of future usage spikes, such as a new image host or a CMS change to URL structure.
Copy link to headingRelated resources
- Explore Vercel’s image optimization cost guidance for reducing billed usage.
- Understand Vercel’s image optimization pricing, limits, and usage billing meters.
- Configure Next.js images using documented sizing, quality, format, and caching.
- Learn how to invalidate cached images through Vercel’s purging tools.
- Explore advanced Next.js image optimization techniques for responsive production applications.
Copy link to headingFrequently asked questions
Why is my Vercel image transformation count high?
One source image can produce multiple variants for different widths, quality settings, and output formats. Each requested variant can incur a transformation on a cache miss or stale revalidation. Short cache lifetimes and changing source URLs, such as URLs with unique query parameters, can increase usage.
Does every srcset width create a billed transformation?
No. Generating a srcset only provides candidate image URLs for the browser. Vercel transforms a variant when it is requested and requires optimization because of a cache miss or stale revalidation. An accurate sizes prop helps browsers select an appropriate width.
Does redeploying clear the image cache?
No. Redeploying does not automatically clear Vercel’s image cache. To refresh an image, invalidate its cached variants or change its source URL. For local images, changing the file contents and redeploying changes the content hash used in the cache key.
Does increasing minimumCacheTTL reduce costs?
It can reduce transformation and cache-write charges for images requested repeatedly over time. A longer TTL allows cached variants to remain fresh longer. The effective cache lifetime is the greater of minimumCacheTTL and the upstream cache lifetime. Use longer TTLs for stable images, with invalidation or versioned URLs when content changes.
Should I disable image optimization?
Use the unoptimized prop selectively for images that gain little from optimization, such as small icons, SVGs, or animated GIFs. Disabling optimization globally can increase transferred bytes and slow image loading, especially for large photos. Compare total delivery cost and image performance before making the change.
Why do new images return HTTP 402 on Hobby?
When a Hobby team exceeds its Image Optimization usage limits, new image optimizations return HTTP 402. Previously optimized images can continue to load from cache, which explains why only some images may fail. Check your team’s usage, reduce unnecessary optimizations, or upgrade to Pro for additional usage.