Cloudinary vs Sanity CDN for Next.js Images: An Honest Comparison

By Nayan Kyada · · 5 min read

Part of The Sanity + Next.js Guide

Choosing between Cloudinary and Sanity's built-in CDN for image delivery in a Next.js project is not obvious. Both serve modern formats, both integrate with next/image, and both have free tiers that look generous until you hit a specific threshold. This comparison is based on production projects where I had to justify the choice to a client paying the bill.

What each system actually does

Sanity stores images in its asset pipeline and exposes them through cdn.sanity.io. Every image you upload gets a stable URL, and you append query parameters — ?w=800&h=600&fit=crop&auto=format — to transform it on the fly. The auto=format flag is what tells Sanity's CDN to serve WebP or AVIF depending on the Accept header. There is no separate transformation pipeline to configure; it's baked in.

Cloudinary is a dedicated media platform. It has its own SDK, its own URL structure, and a much wider transformation surface: AI-based background removal, generative fill, face detection cropping, named transformations you can version and reuse. For a Next.js project, you typically use next-cloudinary or construct URLs manually with the Cloudinary URL builder.

For a content site running on Sanity CMS, Sanity's CDN covers 90% of what you need. For a product or e-commerce site where you're doing complex image operations at scale, Cloudinary earns its seat.

Format support and transformation depth

CapabilitySanity CDNCloudinary
WebP delivery✓ (auto=format)✓ (f_auto)
AVIF delivery
Crop / resize
Focal-point crop (hotspot)✓ (via @sanity/image-url)✓ (face detect or manual)
Background removal✓ (AI, paid)
Generative fill / expand✓ (paid)
Named / versioned transforms
Video transcoding
Signed URLs
On-upload metadata (dominant colour, LQIP)Partial (requires fetch)

Sanity stores a palette and metadata block on every image asset at upload time. That means dominant colour is free at query time — no extra API call. Cloudinary can return metadata but only if you make a separate API request or use its analysis add-on.

Integration with next/image

Both work as next/image loaders. Here is the minimal Sanity loader:

// lib/sanity-image-loader.ts
import { SanityImageSource } from '@sanity/image-url/lib/types/types'
import imageUrlBuilder from '@sanity/image-url'
import { client } from './sanity.client'
 
const builder = imageUrlBuilder(client)
 
export function sanityLoader({
  src,
  width,
  quality,
}: {
  src: string
  width: number
  quality?: number
}) {
  // src is the raw Sanity image reference URL
  return builder
    .image(src)
    .width(width)
    .quality(quality ?? 80)
    .auto('format')
    .url()
}

And a comparable Cloudinary loader:

// lib/cloudinary-loader.ts
export function cloudinaryLoader({
  src,
  width,
  quality,
}: {
  src: string
  width: number
  quality?: number
}) {
  const params = [
    'f_auto',
    'c_limit',
    `w_${width}`,
    `q_${quality ?? 'auto'}`,
  ].join(',')
  // src is the Cloudinary public_id
  return `https://res.cloudinary.com/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/image/upload/${params}/${src}`
}

Both produce responsive URLs that next/image uses for its srcset. The difference is where the asset lives. If you're already on Sanity, uploading a second copy to Cloudinary for delivery is unnecessary overhead — both storage cost and upload latency during content entry.

Pricing gap at real traffic levels

This is where the decision often ends.

PlanSanity CDNCloudinary
Free bandwidthIncluded in Sanity plan (unlimited via CDN)25 GB / month
Free transformationsUnlimited (URL param based)25 credits (~25k transforms)
First paid tierSanity Growth: $15/month (not CDN-gated)Plus: ~$89/month
Transformation overageN/A$0.05 per credit beyond plan
Storage overageSanity assets count against project quota$0.04/GB beyond 25 GB

Sanity's CDN does not charge per transformation. You pay for the Sanity plan, and image delivery is not the thing that pushes you up a tier — document count and seat count are. A marketing site with 500 posts and heavy image traffic will stay on the Sanity free or Growth plan without Cloudinary ever entering the equation.

Cloudinary's 25 credit limit sounds workable until you factor in that one f_auto,c_fill,w_800 transform on a new URL counts as one credit. A site that generates many URL variants (responsive widths × crops × quality) can hit 25k transforms in a week during a crawl or cache-warming run.

When to use Cloudinary alongside Sanity

There is a valid pattern: store originals in Sanity, sync or upload to Cloudinary for specific assets that need advanced transforms. I've done this for one client who needed AI background removal on product images. The Sanity webhook fires on publish, a route handler uploads the asset to Cloudinary, stores the Cloudinary public ID back on the Sanity document, and the frontend reads that ID for product images only.

That hybrid only makes sense if you have a genuine need for Cloudinary's AI layer. Running the full stack through Cloudinary when your assets already live in Sanity doubles your storage costs and complicates your content pipeline for no meaningful delivery improvement.

Sanity CDN vs Cloudinary: the honest pick

If your project is a Next.js + Sanity CMS content site — blog, marketing site, documentation, editorial — use Sanity's CDN with @sanity/image-url and the auto=format flag. You get WebP/AVIF, focal-point crops, dominant colour for placeholders, and zero extra cost. There is no CDN configuration to maintain.

If your project is a product catalogue, marketplace, or app where images need generative AI operations, face detection, or heavily versioned named transforms, Cloudinary is worth the $89/month starting point. Build the hybrid pattern only if Sanity is your source of truth and Cloudinary handles a specific transform layer.

The worst outcome is paying for Cloudinary's Plus plan on a content site that auto=format would have served just as well.