How to Use Cloudinary with Astro: Complete Guide
Step-by-step guide to integrating Cloudinary with your Astro website.
Cloudinary is a cloud-based media management platform that handles image and video optimization, transformation, and delivery through a global CDN. Instead of manually resizing images, converting formats, and setting up a CDN yourself, Cloudinary does all of this through URL parameters. Upload an image once, and you can serve it in any size, format, or quality by changing the URL.
For Astro sites where images make up the bulk of page weight, Cloudinary can dramatically improve your Core Web Vitals scores without you writing optimization logic.
Prerequisites
- Node.js 18+
- An Astro project (
npm create astro@latest) - A Cloudinary account (free tier gives you 25 monthly credits, which covers roughly 25GB of bandwidth or 25,000 transformations)
Installation
Install the Cloudinary SDK and the Astro-friendly helper:
npm install cloudinary @cloudinary-util/url-loader
The @cloudinary-util/url-loader package makes it easy to generate optimized Cloudinary URLs without the full SDK weight on the client.
Configuration
Add your Cloudinary credentials to .env:
PUBLIC_CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
The cloud name is public (it appears in image URLs), but the API key and secret should stay server-side.
Create a helper for generating Cloudinary URLs:
// src/lib/cloudinary.ts
const CLOUD_NAME = import.meta.env.PUBLIC_CLOUDINARY_CLOUD_NAME;
export function getCloudinaryUrl(
publicId: string,
options: {
width?: number;
height?: number;
format?: "auto" | "webp" | "avif";
quality?: "auto" | number;
crop?: "fill" | "fit" | "scale" | "thumb";
} = {}
) {
const {
width,
height,
format = "auto",
quality = "auto",
crop = "fill",
} = options;
const transforms = [
`f_${format}`,
`q_${quality}`,
width && `w_${width}`,
height && `h_${height}`,
crop && `c_${crop}`,
]
.filter(Boolean)
.join(",");
return `https://res.cloudinary.com/${CLOUD_NAME}/image/upload/${transforms}/${publicId}`;
}
Basic Usage
Use the helper in your Astro components to serve optimized images:
---
// src/components/blog/PostImage.astro
import { getCloudinaryUrl } from "../../lib/cloudinary";
export interface Props {
publicId: string;
alt: string;
width?: number;
height?: number;
}
const { publicId, alt, width = 800, height = 450 } = Astro.props;
const src = getCloudinaryUrl(publicId, { width, height });
const srcWebp = getCloudinaryUrl(publicId, { width, height, format: "webp" });
const srcAvif = getCloudinaryUrl(publicId, { width, height, format: "avif" });
---
<picture>
<source srcset={srcAvif} type="image/avif" />
<source srcset={srcWebp} type="image/webp" />
<img
src={src}
alt={alt}
width={width}
height={height}
loading="lazy"
decoding="async"
/>
</picture>
Use it in your blog posts or pages:
---
import PostImage from "../components/blog/PostImage.astro";
---
<PostImage
publicId="blog/my-article-hero"
alt="A descriptive alt text for the image"
width={1200}
height={630}
/>
For responsive images with multiple sizes:
---
import { getCloudinaryUrl } from "../../lib/cloudinary";
const publicId = "blog/hero-image";
const sizes = [400, 800, 1200];
const srcset = sizes
.map((w) => `${getCloudinaryUrl(publicId, { width: w, format: "webp" })} ${w}w`)
.join(", ");
---
<img
src={getCloudinaryUrl(publicId, { width: 800, format: "webp" })}
srcset={srcset}
sizes="(max-width: 600px) 400px, (max-width: 1024px) 800px, 1200px"
alt="Responsive hero image"
loading="lazy"
/>
Production Tips
Always use
f_autoandq_auto. These parameters let Cloudinary choose the best format (WebP, AVIF, or JPEG) and quality level based on the requesting browser. This alone can cut image sizes by 40-60%.
Set explicit width and height. Providing dimensions in your <img> tags prevents layout shift (CLS). Cloudinary makes this easy because you control the output size through URL parameters.
Use named transformations for consistency. Instead of repeating transform parameters, create named transformations in your Cloudinary dashboard (like "blog_hero" or "thumbnail"). Reference them with t_blog_hero in the URL.
Serve video through Cloudinary too. Cloudinary handles video optimization with the same URL-based approach. Automatic format selection, adaptive bitrate, and thumbnail generation all work through URL parameters.
Enable fetch mode for external images. You can optimize images from any URL by using Cloudinary's fetch feature: https://res.cloudinary.com/your_cloud/image/fetch/f_auto,q_auto/https://example.com/image.jpg. No upload needed.
Alternatives to Consider
- Cloudflare R2 + Images if you want cheaper storage with zero egress fees and basic image resizing through Cloudflare's network.
- ImageKit if you want similar URL-based transformations with a slightly simpler pricing model and built-in DAM features.
- Astro's built-in Image component if your images are local and you want zero external dependencies. Astro optimizes images at build time using Sharp.
Wrapping Up
Cloudinary handles the entire image optimization pipeline so you do not have to. Upload once, serve everywhere, in any size and format. For Astro sites where page speed matters (and it always does), offloading image optimization to Cloudinary is one of the highest-impact improvements you can make with minimal code changes.
Related Articles
How to Use Algolia with Astro: Complete Guide
Step-by-step guide to integrating Algolia with your Astro website.
How to Integrate Auth0 with Astro: Complete Guide
Step-by-step guide to integrating Auth0 with your Astro website. Setup, configuration, and best practices.
How to Use AWS Amplify with Astro: Complete Guide
Step-by-step guide to integrating AWS Amplify with your Astro website.