Skip to content

Vercel’s free image limit: serving Next.js pictures from Sanity

Vercel’s free Hobby plan includes 5,000 image transformations a month, and image-heavy sites reach it fast. A short Next.js loader hands every resize to Sanity’s image CDN instead.

Cover: shop photos of a cap, mug, tee, hoodie and apron growing in size, a Sanity image address, and Vercel handing the resizing down to Sanity in a panel of its own

5,000 resizes a month

Vercel’s free Hobby plan includes 5,000 image transformations a month, and Vercel emails the team as usage nears that limit. Sites with many large images reach it fastest.

The fix takes one short file and two lines of configuration: every picture gets resized by Sanity, the content platform that already stores it.

How Vercel counts resizes

Next.js sends each visitor a picture sized for their screen: a narrow copy for a phone, a large one for a wide monitor, so a single photo can exist in up to 16 widths. On Vercel, the default Next.js picture path runs through Vercel’s own resizer, and each new combination of picture, width and quality counts once toward the monthly allowance. Preview deployments count too.

The count multiplies fast. A shop page with 100 product photos offers each in up to 16 widths, 1,600 possible resizes, so three such pages pass the monthly 5,000.

At 100%

Hobby keeps the bill at zero. The cost lands on the page instead: each picture still waiting for its first resize returns status 402, and the visitor sees its description text where the photo should be. Pictures resized earlier keep loading, so new products and new pages show empty frames first.

Pictures straight from Sanity

Pictures stored in Sanity can skip that resizer, since Sanity’s image CDN resizes on request. Next.js documents a way to hand picture work to a service like that, a custom image loader, and lists Sanity among its examples. The loader is one short file, here data/imageLoader.js:

javascript
'use client'

export default function imageLoader({ src, width, quality }) {
  if (!src.startsWith('https://cdn.sanity.io/') || /\.(svg|gif)(\?|$)/i.test(src)) return src

  const [base, query = ''] = src.split('?')
  const params = new Map(
    query
      .split('&')
      .filter(Boolean)
      .map((pair) => {
        const at = pair.indexOf('=')
        return at < 0 ? [pair, ''] : [pair.slice(0, at), pair.slice(at + 1)]
      }),
  )

  const w = Number(params.get('w'))
  const h = Number(params.get('h'))
  if (w && h) params.set('h', String(Math.round((h * width) / w)))
  params.set('w', String(width))
  if (!params.has('q')) params.set('q', String(quality || 75))
  if (!params.has('fit')) params.set('fit', 'max')
  if (!params.has('auto')) params.set('auto', 'format')

  return `${base}?${[...params].map(([k, v]) => (v ? `${k}=${v}` : k)).join('&')}`
}

And two lines in next.config.mjs point Next.js at it:

javascript
images: {
  remotePatterns: [{ protocol: 'https', hostname: 'cdn.sanity.io' }],
  loader: 'custom',
  loaderFile: './data/imageLoader.js',
},

For every width a picture offers, the loader returns a Sanity address with:

  • w, the width the screen needs
  • q=75, unless the address already sets a quality, such as 100 for a full-screen view
  • fit=max, so a picture stays at or below its original size
  • auto=format, so Sanity sends WebP or AVIF wherever the browser takes it

Crops and focus points already in the address ride along, and a fixed cut with both width and height keeps its shape at every width. SVG icons, GIFs and files served from the site itself load as they are.

Default path and loader

Default pathCustom loader
Who resizes the picturesVercelSanity’s image CDN
What counts toward Vercel’s 5,000Each new picture, width and qualityZero
Monthly limit in play5,000 transformations100 GB of Sanity bandwidth

What stays private

Pictures in Sanity are already public on cdn.sanity.io, so the change alters only which address asks for them. Picture addresses carry zero keys, the Sanity write token stays on the server, and a site with a Content Security Policy lists cdn.sanity.io among its image sources.

The trade-off

Picture bandwidth moves from Vercel’s meter to Sanity’s. Both free plans allow 100 GB a month, and Sanity’s resizing draws on that bandwidth alone.

Sanity’s free plan stops serving at its cap and emails at 80%, so an early warning stays in place. Every site reading from the same Sanity project draws on the same 100 GB.

When the default fits

The default Next.js picture path suits a site of a few pages and a handful of pictures. A shop, a gallery or a growing catalogue multiplies the pictures, and the loader is best added before the first usage email, while every picture still loads.

Websites & AppsSites, stores and web apps on Shopify, Framer, Webflow or custom code, integrations with CMS, CRM and more, servers, and security.

Sources

Published
Author
Booban Net.

Resources

Your next read

View all

Ready when you are

Tell us what you need and a written quote comes back within a working day.

Phone or WhatsApp
What do you need

By sending, you agree to our Privacy Policy and Terms.

or connect with us via :
WhatsAppEmail