Cloudinary 사용법: URL 하나로 이미지 최적화하기
웹 서비스에서 이미지가 차지하는 비중이 정말 크죠. HTTP Archive에 따르면 평균 웹 페이지 용량의 절반 가까이가 이미지라고 합니다. 그래서 이미지를 얼마나 잘 관리하느냐가 사이트 성능에 직접적인 영향을 미치는데요.
이미지를 직접 서버에서 처리하려면 Sharp나 libvips 같은 라이브러리를 설치하고, 리사이징·포맷 변환·캐싱 로직을 일일이 구현해야 합니다. 소규모 프로젝트에서는 이 정도면 충분하지만 이미지가 수만 장 이상이거나 글로벌 사용자를 대상으로 한다면 CDN 배포와 on-the-fly 변환까지 직접 구축하기가 만만치 않죠.
Cloudinary는 이런 고민을 URL 하나로 해결해 주는 서비스입니다. 클라우드에서 이미지를 관리하고 최적화해 주거든요. 이미지를 업로드하면 전 세계 CDN으로 자동 배포되고, URL에 파라미터만 추가하면 리사이징, 크롭, 포맷 변환, 품질 조절까지 서버 코드 한 줄 없이 처리할 수 있어요. 이 글에서는 Cloudinary의 핵심 기능을 하나씩 살펴보겠습니다.
계정 만들기
Cloudinary는 무료 플랜을 제공하고 있어요. 가입도 간단합니다. 신용카드 없이 가입할 수 있고 매월 25 크레딧이 주어지는데요. 크레딧 하나당 변환 1,000건, 저장 공간 1GB, 전송 대역폭 1GB에 해당합니다. 개인 프로젝트나 블로그에 쓰기에는 넉넉한 양이에요.
cloudinary.com에서 가입하면 대시보드에서 세 가지 정보를 확인할 수 있습니다.
cloud_name— 계정을 식별하는 고유 이름api_key— API 인증용 키api_secret— API 인증용 비밀 키
이 정보는 이후 SDK를 설정하거나 API를 호출할 때 필요하니 메모해 두세요.
URL 기반 이미지 변환
Cloudinary에서 가장 편한 기능은 URL만으로 이미지를 변환할 수 있다는 점이에요. 서버에 별도의 이미지 처리 로직을 구현할 필요 없이 URL 경로에 변환 파라미터를 넣으면 Cloudinary가 알아서 처리해 줍니다.
기본적인 이미지 URL 구조는 이렇습니다.
https://res.cloudinary.com/{cloud_name}/image/upload/{변환}/{public_id}.{포맷}
예를 들어 sample.jpg라는 이미지를 업로드했다면 원본 URL은 이렇게 됩니다.
https://res.cloudinary.com/demo/image/upload/sample.jpg
여기에 변환 파라미터를 추가하면 이미지가 실시간으로 변환됩니다. 너비를 400px로 줄이고 싶다면 w_400을 넣으면 돼요.
https://res.cloudinary.com/demo/image/upload/w_400/sample.jpg
400x300 크기로 얼굴 중심 크롭을 하고 싶다면 이렇게 쓸 수 있습니다.
https://res.cloudinary.com/demo/image/upload/c_fill,g_face,w_400,h_300/sample.jpg
같은 변환 그룹 안에서는 쉼표(,)로 파라미터를 연결하고 변환 그룹끼리는 슬래시(/)로 구분합니다. 파라미터 이름이 직관적이라 금방 익숙해질 거예요.
자주 쓰는 변환 파라미터
URL 변환에서 가장 많이 쓰는 파라미터를 몇 가지 정리해 볼게요.
리사이징
너비와 높이를 지정하는 기본적인 리사이징입니다.
# 너비 800px로 리사이징 (높이는 비율 유지)
w_800
# 너비 400, 높이 300으로 맞추기
w_400,h_300
# 원본의 50%로 축소
w_0.5
크롭 모드
이미지를 잘라내는 방식을 c_ 파라미터로 지정합니다.
# 지정된 크기에 꽉 채우기 (비율 유지, 넘치는 부분 잘라냄)
c_fill,w_400,h_300
# 지정된 크기 안에 맞추기 (비율 유지, 여백 가능)
c_fit,w_400,h_300
# 지정된 크기에 맞추되 여백을 배경색으로 채우기
c_pad,w_400,h_300,b_white
# 얼굴 중심으로 자동 크롭
c_fill,g_face,w_200,h_200
# AI가 판단한 관심 영역 기준 크롭
c_fill,g_auto,w_400,h_300
g_face는 얼굴 인식 기반으로 크롭 위치를 잡아주고, g_auto는 AI가 이미지에서 가장 중요한 영역을 찾아서 크롭합니다. 프로필 이미지나 썸네일을 만들 때 특히 유용하죠.
포맷 변환
# WebP로 변환
f_webp
# AVIF로 변환
f_avif
# PNG로 변환
f_png
품질 조절
# 품질 80%
q_80
# 자동 품질 (기본: good)
q_auto
# 자동 품질 수준 지정
q_auto:best
q_auto:good
q_auto:eco
q_auto:low
효과
# 흑백으로 변환
e_grayscale
# 블러 처리 (강도 100~2000)
e_blur:500
# 둥근 모서리 (프로필 이미지용)
r_max
# 회전 (각도)
a_90
자동 최적화: f_auto와 q_auto
Cloudinary에서 가장 실용적인 기능을 하나만 꼽으라면 f_auto와 q_auto를 들겠어요. 이 두 파라미터만 URL에 추가하면 별다른 설정 없이 이미지 용량을 크게 줄일 수 있거든요.
f_auto는 브라우저가 보내는 Accept 헤더를 분석해서 지원하는 가장 효율적인 포맷으로 자동 변환합니다. Chrome이면 AVIF나 WebP로, Safari면 WebP로, 아주 오래된 브라우저면 원본 JPEG 그대로 보내주는 식이에요. HTML의 <picture> 요소로 포맷 분기를 직접 구현하는 것과 같은 효과를 URL 파라미터 하나로 얻을 수 있는 셈이죠.
q_auto는 이미지 내용을 분석해서 시각적 품질을 유지하면서 최대한 용량을 줄여줍니다. 실제로 얼마나 차이가 나는지 볼까요?
| 설정 | 용량 | 감소율 |
|---|---|---|
| 원본 | 569 KB | - |
q_auto:best | 65.9 KB | 88% |
q_auto:good (기본) | 56.9 KB | 90% |
q_auto:eco | 45.0 KB | 92% |
q_auto:low | 35.0 KB | 94% |
기본값인 q_auto:good만 써도 원본 대비 90%나 줄어듭니다. 거의 모든 상황에서 이 두 파라미터를 기본으로 넣어두는 것을 추천합니다.
https://res.cloudinary.com/demo/image/upload/f_auto,q_auto/sample.jpg
한 가지 알아둘 점은, f_auto는 브라우저의 요청 시점에 동작하기 때문에 업로드 시 eager 변환에서는 효과가 없다는 것입니다. 업로드 시 미리 변환을 걸어두려면 f_webp처럼 포맷을 명시해야 합니다.
Node.js SDK 설치와 설정
브라우저에서 직접 URL을 조립해도 되지만, 서버에서 이미지를 업로드하거나 URL을 프로그래밍 방식으로 생성하려면 SDK를 쓰는 게 편합니다.
bun add cloudinary
# 또는
npm install cloudinary
SDK를 설정하는 방법은 두 가지예요. 코드에서 직접 설정하거나 환경 변수를 사용할 수 있습니다.
import { v2 as cloudinary } from "cloudinary";
// 방법 1: 코드에서 직접 설정
cloudinary.config({
cloud_name: "my_cloud",
api_key: "123456789012345",
api_secret: "abcdefghijklmnopqrstuvwxyz",
secure: true,
});
환경 변수를 쓰면 코드에 인증 정보를 넣지 않아도 됩니다.
export CLOUDINARY_URL=cloudinary://123456789012345:abcdefghijklmnopqrstuvwxyz@my_cloud
CLOUDINARY_URL 환경 변수가 설정되어 있으면 SDK가 자동으로 읽어오기 때문에 cloudinary.config()를 호출하지 않아도 됩니다.
이미지 업로드
SDK로 이미지를 업로드하는 코드를 살펴볼게요.
import { v2 as cloudinary } from "cloudinary";
// 로컬 파일 업로드
const result = await cloudinary.uploader.upload("photo.jpg", {
folder: "blog",
public_id: "my-post-cover",
});
console.log(result.secure_url);
// https://res.cloudinary.com/my_cloud/image/upload/v1234567890/blog/my-post-cover.jpg
folder 옵션으로 경로를 지정할 수 있고 public_id로 파일 이름을 정할 수 있어요. 업로드 결과에는 secure_url, width, height, format, bytes 같은 정보가 포함됩니다.
원격 URL에 있는 이미지도 바로 업로드할 수 있습니다.
const result = await cloudinary.uploader.upload(
"https://example.com/photo.jpg",
{ folder: "external" },
);
업로드 시 변환을 미리 적용해 둘 수도 있는데요. eager 옵션을 사용하면 됩니다.
const result = await cloudinary.uploader.upload("photo.jpg", {
eager: [
{ width: 400, height: 300, crop: "fill" },
{ width: 200, height: 200, crop: "thumb", gravity: "face" },
],
});
이렇게 하면 원본 이미지와 함께 변환된 버전들이 미리 생성되어 CDN에 캐싱됩니다. 자주 사용하는 크기가 정해져 있다면 첫 요청 시 변환 지연 없이 바로 서빙할 수 있어서 좋습니다.
SDK로 URL 생성하기
이미 업로드된 이미지의 변환 URL을 SDK로 만들 수도 있습니다. URL을 직접 문자열로 조립하는 것보다 실수가 적고 타입 안전하죠.
import { v2 as cloudinary } from "cloudinary";
// 기본 URL 생성
const url = cloudinary.url("blog/my-post-cover.jpg", {
secure: true,
});
// https://res.cloudinary.com/my_cloud/image/upload/blog/my-post-cover.jpg
// 변환이 적용된 URL 생성
const optimizedUrl = cloudinary.url("blog/my-post-cover.jpg", {
width: 800,
crop: "fill",
fetch_format: "auto",
quality: "auto",
secure: true,
});
// https://res.cloudinary.com/my_cloud/image/upload/c_fill,f_auto,q_auto,w_800/blog/my-post-cover.jpg
fetch_format: "auto"가 URL의 f_auto에 대응하고, quality: "auto"가 q_auto에 대응합니다.
반응형 이미지 서빙
화면 크기에 따라 다른 크기의 이미지를 서빙하고 싶을 때도 Cloudinary가 유용해요. HTML의 srcset 속성과 함께 쓰면 브라우저가 알아서 적절한 크기를 골라갑니다.
<img
src="https://res.cloudinary.com/demo/image/upload/w_800,f_auto,q_auto/sample.jpg"
srcset="
https://res.cloudinary.com/demo/image/upload/w_400,f_auto,q_auto/sample.jpg 400w,
https://res.cloudinary.com/demo/image/upload/w_800,f_auto,q_auto/sample.jpg 800w,
https://res.cloudinary.com/demo/image/upload/w_1200,f_auto,q_auto/sample.jpg 1200w
"
sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px"
alt="반응형 이미지"
loading="lazy"
/>
URL의 w_ 값만 바꾸면 되니까 같은 이미지를 여러 크기로 미리 만들어 둘 필요가 없습니다. 요청이 들어올 때 Cloudinary가 변환해서 CDN에 캐싱하기 때문에 두 번째 요청부터는 바로 서빙됩니다.
Fetch 모드로 외부 이미지 최적화
이미 다른 서버에 올려둔 이미지도 Cloudinary를 통해 최적화할 수 있습니다. image/upload 대신 image/fetch를 쓰면 돼요.
https://res.cloudinary.com/demo/image/fetch/f_auto,q_auto,w_800/https://example.com/photo.jpg
이렇게 하면 Cloudinary가 원본 이미지를 가져와서 변환한 뒤 CDN으로 서빙합니다. 기존 서비스의 이미지를 건드리지 않고도 최적화 효과를 누릴 수 있어서, 레거시 시스템에 Cloudinary를 점진적으로 도입할 때 유용한 방법이에요.
프론트엔드 프레임워크 연동
React나 Next.js 같은 프론트엔드 프레임워크에서는 Cloudinary URL을 그대로 <img> 태그의 src에 넣으면 됩니다. 별도의 프레임워크용 라이브러리 없이도 충분히 쓸 수 있어요.
function ImageCard({ publicId, alt }) {
const cloudName = "my_cloud";
const baseUrl = `https://res.cloudinary.com/${cloudName}/image/upload`;
return (
<img
src={`${baseUrl}/c_fill,w_400,h_300,f_auto,q_auto/${publicId}`}
alt={alt}
loading="lazy"
width={400}
height={300}
/>
);
}
만약 Astro를 사용하고 있다면 Astro의 Image 컴포넌트와 Cloudinary를 함께 쓸 수도 있습니다. astro.config.mjs에서 Cloudinary 도메인을 허용해 주면 외부 이미지로 취급되어 Astro의 최적화 파이프라인도 함께 적용됩니다.
export default defineConfig({
image: {
domains: ["res.cloudinary.com"],
},
});
다만 이 경우 Cloudinary의 변환과 Astro의 변환이 이중으로 적용되니, 어느 쪽에서 최적화를 담당할지 정해두는 게 좋습니다. Cloudinary URL에서 이미 리사이징과 포맷 변환을 했다면 Astro 쪽에서는 추가 변환 없이 그대로 쓰는 것이 효율적이에요.
이미지 삭제와 관리
업로드한 이미지를 삭제하거나 이름을 바꾸는 것도 SDK로 할 수 있습니다.
import { v2 as cloudinary } from "cloudinary";
// 단일 이미지 삭제
await cloudinary.uploader.destroy("blog/my-post-cover");
// 이름 변경
await cloudinary.uploader.rename("blog/old-name", "blog/new-name");
// 폴더 안의 리소스 목록 조회
const resources = await cloudinary.api.resources({
type: "upload",
prefix: "blog/",
max_results: 30,
});
대시보드에서도 이미지를 확인하고 관리할 수 있지만, 자동화가 필요하면 SDK의 Admin API를 쓰는 게 편합니다.
마치며
Cloudinary는 이미지 업로드, 변환, 최적화, CDN 배포를 하나의 URL로 처리할 수 있는 서비스예요. 특히 f_auto와 q_auto만 URL에 붙여도 이미지 용량을 90% 가까이 줄일 수 있어서 웹 성능을 개선하는 가장 간단한 방법 중 하나라고 할 수 있어요.
물론 모든 프로젝트에 Cloudinary가 필요한 것은 아닙니다. 이미지가 많지 않은 정적 블로그라면 Sharp로 빌드 시 처리하거나, Astro의 이미지 최적화처럼 프레임워크 내장 기능을 쓰는 것으로 충분할 수 있습니다. 하지만 사용자가 올리는 이미지를 실시간으로 변환해야 하거나, 글로벌 CDN이 필요하거나, 이미지 처리 인프라를 직접 관리하고 싶지 않다면 Cloudinary가 좋은 선택이 될 거예요.
This work is licensed under
CC BY 4.0