When I first heard about Cloudinary, I thought it was another image hosting site. Like Imgur but for professionals. Upload a photo, share a link.
That’s technically correct but completely misses the point.
Cloudinary is an image CDN with a transformation API built into the URL. You don’t need a server, you don’t need to write code to resize images, you don’t need to generate thumbnails. You just change the URL.
That realization hit me when I saw the transform parameters for the first time — w_800,h_400,c_fill,f_auto,q_auto embedded directly in a URL. I’d seen URLs like that on other sites before and always wondered what those strange parameters meant.
Now I know. And it’s genuinely clever.
What Cloudinary actually is
Cloudinary is three things in one:
Storage — your images live on Cloudinary’s servers, not in your repo or on your hosting.
CDN — images are served from edge servers close to visitors. Someone in Europe gets the image from a European server. Someone in Asia gets it from an Asian server.
Transformation API — every image can be resized, cropped, reformatted, and compressed on demand by changing the URL. No image editing software needed. No server code needed.
For a static Astro site, the last point is particularly valuable. Astro optimizes local images at build time — which is great, but means every size variant gets generated during the build. Cloudinary generates variants on demand, at the CDN edge, without touching your build.
Setting up Cloudinary
Step 1: Create a free account
Go to Cloudinary and create an account. Cloudinary currently measures Free-plan usage in credits rather than promising separate fixed storage and bandwidth quotas. Storage, delivery bandwidth, and newly generated transformations all use that allowance.
That model can still be practical for a small content site, but check the current Cloudinary plans before treating a quota as permanent.
Step 2: Find your cloud name
After signing up, your cloud name appears in the dashboard — top left corner. It looks like di9xkgxd8 or a custom name you chose during registration.
This is part of every Cloudinary URL. Keep it handy.
Step 3: Upload your first image
Go to Media Library → click Upload → drag and drop an image file.
Understanding the Cloudinary URL
When you upload an image, Cloudinary gives you a URL like this:
https://res.cloudinary.com/di9xkgxd8/image/upload/v1781786068/hello-world.jpg
Breaking it down:
https://res.cloudinary.com/ → Cloudinary CDN domain
di9xkgxd8/ → your cloud name
image/upload/ → resource type and action
v1781786068/ → version number (auto-generated)
hello-world.jpg → your filename
That URL serves the original image. Now the interesting part.
URL transforms — the magic part
Between /upload/ and the version number, you can insert transformation parameters:
https://res.cloudinary.com/di9xkgxd8/image/upload/[TRANSFORMS]/v123/hello-world.jpg
Common transforms:
w_800 → width 800px
h_400 → height 400px
c_fill → crop to fill (smart cropping)
c_fit → fit within dimensions (no cropping)
f_auto → serve WebP to browsers that support it, JPEG otherwise
q_auto → automatic quality optimization
Combine them with commas:
# Resize to 1200x630, WebP, auto quality — perfect for hero images
w_1200,h_630,c_fill,f_auto,q_auto
# Small thumbnail, 400x210
w_400,h_210,c_fill,f_auto,q_auto
Full hero image URL with transforms:
https://res.cloudinary.com/di9xkgxd8/image/upload/w_1200,h_630,c_fill,f_auto,q_auto/v1781786068/hello-world.jpg
Using Cloudinary in Astro
The simplest way: store the URL as a string in frontmatter.
Step 1: Add a Cloudinary field to the schema
const posts = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/posts' }),
schema: ({ image }) => z.object({
title: z.string(),
description: z.string(),
publishedAt: z.date(),
heroImage: image().optional(), // local image
heroImageCloudinary: z.string().optional(), // cloudinary URL
heroImageAlt: z.string().optional(),
}),
})
Having both fields lets you use local images during development and Cloudinary in production — or mix both depending on the post.
Step 2: Add the URL to frontmatter
---
title: What is a Static Site?
heroImageCloudinary: https://res.cloudinary.com/di9xkgxd8/image/upload/w_1200,h_630,c_fill,f_auto,q_auto/v1781786068/hello-world.jpg
heroImageAlt: Static site generation explained
---
Put the transforms directly in the URL you store. This way the frontmatter always contains the final, optimized URL — no transformation logic needed in the template.
Step 3: Display in the template
{post.data.heroImage ? (
<div class="hero-image">
<Image
src={post.data.heroImage}
alt={post.data.heroImageAlt || post.data.title}
width={1200}
height={630}
/>
</div>
) : post.data.heroImageCloudinary ? (
<div class="hero-image">
<img
src={post.data.heroImageCloudinary}
alt={post.data.heroImageAlt || post.data.title}
width={1200}
height={630}
loading="lazy"
/>
</div>
) : null}
Local image → uses Astro <Image /> component for build-time optimization.
Cloudinary image → uses plain <img> tag since Cloudinary already handles optimization via URL.
Local vs Cloudinary: honest comparison
Local (src/assets/) | Cloudinary | |
|---|---|---|
| Setup | Zero — just add files | Free account + upload |
| Optimization | Astro at build time | Cloudinary at CDN edge |
| Repo impact | Images committed to Git | No repo impact |
| Transform control | Fixed at build time | Dynamic via URL |
| CDN | Vercel/Netlify CDN | Cloudinary global CDN |
| Cost | Free | Free tier, then paid |
| Best for | Small-medium sites | High-volume content |
When to use which
Use local images when:
- Your site has fewer than ~100 posts
- You prefer everything in one place
- Build times aren’t a concern
- The repo size is manageable
Use Cloudinary when:
- Your repo is getting heavy from images
- You publish frequently with multiple images per post
- You need the same image at many different sizes
- You want to serve the absolute best format per browser automatically
The practical path:
Start with local images. They’re simpler. When the repo starts feeling heavy or build times creep up, migrate to Cloudinary — update the frontmatter fields, remove the local files, done. The templates handle both.
This is exactly how astro-content-lab is set up: local images for the demo content, Cloudinary support ready for when it’s needed.
A note on repo size as your site grows
This came up when building this project: what happens when you have hundreds of images in src/assets/?
The repo grows. Every git clone takes longer. Every new developer on the project downloads megabytes of images they may not need.
Some teams solve this with Git LFS (Large File Storage) — a Git extension that stores large files separately. It works but adds complexity.
The cleaner solution for a content site: move images to Cloudinary when the repo hits a size that slows you down. Commit only code and content. Let the CDN handle the media.
There’s no hard rule on when to make the switch. When cloning the repo starts taking noticeably longer than it should, that’s the signal.