본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 15. 17:07

AI 생성 도구를 위한 Next.js 이미지 전달 최적화 방법

요약

AI 이미지 생성 도구의 성능 병목 현상인 이미지 전달 문제를 해결하기 위한 최적화 방법을 다룹니다. Base64 방식의 한계를 지적하고, Cloudinary와 Next.js를 결합하여 이미지 크기 감소 및 CDN 활용을 통해 LCP를 개선하는 전략을 소개합니다.

핵심 포인트

  • Base64 인코딩 방식은 페이로드 크기 증가와 캐싱 불가 문제를 야기함
  • Cloudinary를 중개 레이어로 사용하여 WebP 변환 및 CDN 전달 구현
  • 이미지 포맷 최적화를 통해 파일 크기를 약 40-60% 감소시킴
  • 임시 저장소 설정을 통해 불필요한 저장 비용을 최소화함
  • LCP(Largest Contentful Paint) 개선을 통한 사용자 체감 성능 향상

제가 unlimited AI image generator free를 대중에게 공개했을 때, 첫 번째 성능 문제는 AI 추론 (Inference)이 아니라 이미지 전달 (Image delivery)이었습니다.

생성된 이미지는 문제없이 도착했습니다. 하지만 이를 사용자의 브라우저로 효율적으로 전달하는 것이 병목 현상이었습니다. 제가 발견한 문제와 이를 어떻게 해결했는지 소개합니다.

문제 (The Problem)

사용자가 이미지를 생성합니다. 모델이 이를 생성합니다. 그다음은 무엇일까요?

단순한 접근 방식: API 응답에 이미지를 base64 문자열로 직접 반환하는 것입니다.

// 단순한 접근 방식 — 작동은 하지만 최적화되지 않음
const imageBuffer = await runInference(prompt);
const base64 = imageBuffer.toString('base64');
...

이 방식의 문제점:

  • 큰 페이로드 크기 (PNG base64는 약 33%의 오버헤드를 추가함)
  • 캐싱 (Caching) 없음
  • CDN 전달 없음
  • 브라우저가 렌더링하기 전에 base64를 디코딩해야 함. 이미지 품질에 대한 인식이 중요한 도구에서 이 문제는 눈에 띄었습니다.

Next.js Image 컴포넌트의 제약 사항

Next.js의 <Image> 컴포넌트는 최적화를 자동으로 처리하지만, 빌드 타임에 크기를 알 수 있거나 승인된 도메인의 이미지에 대해서만 작동합니다.

외부 소스에서 동적으로 생성된 이미지의 경우, remotePatterns를 설정해야 합니다:

// next.config.js
const nextConfig = {
  images: {
...

이 설정이 없으면 <Image>는 동적으로 소싱된 URL에 대해 에러를 발생시킵니다.

Cloudinary 레이어

저는 추론 결과물과 브라우저 전달 사이의 중개자로 Cloudinary를 추가했습니다.

흐름 (The flow):

추론 API (Inference API) → 원본 이미지 (Raw image) → Cloudinary에 업로드 → 
Cloudinary URL 반환 → Next.js <Image> 렌더링 → 
Cloudinary CDN이 브라우저에 WebP 전달

자동으로 적용된 주요 Cloudinary 변환 (Transformations):

// 변환이 적용된 Cloudinary URL
const optimizedUrl = cloudinary.url(publicId, {
  format: 'webp',        // WebP 변환
...

결과:

  • PNG → WebP: 파일 크기 약 40-60% 감소
  • CDN 전달: 전 세계 에지 노드 (Edge nodes) 활용

- 캐싱 (Caching): 반복되는 요청을 즉시 처리

임시 저장소 vs 지속적 저장소 처리 (Handling Temporary vs Persistent Storage)

모든 생성된 이미지가 영구적인 저장 공간을 필요로 하는 것은 아닙니다. 프롬프트가 저장되지 않는 도구의 경우 다음과 같이 처리할 수 있습니다:

// 임시 업로드 — 1시간 후 자동 삭제
const uploadResult = await cloudinary.uploader.upload(imageBuffer, {
  folder: 'generated',
...

이렇게 하면 저장 비용을 최소화할 수 있습니다. 이미지는 다운로드될 수 있을 만큼의 충분한 시간 동안만 존재한 뒤 삭제됩니다.

LCP 영향 (LCP Impact)

Largest Contentful Paint (LCP)는 생성 도구의 체감 성능(perceived performance)에서 가장 중요한 지표입니다. 대부분의 사용자에게 생성된 이미지 자체가 바로 LCP 요소입니다.

LCP를 개선한 변경 사항들:

1. Cloudinary로의 Preconnect (Preconnect to Cloudinary)

<link rel="preconnect" href="https://res.cloudinary.com" />

2. 생성된 이미지에 우선순위 부여 (Priority on the generated image)

<Image
  src={generatedImageUrl}
  alt={altText}
...

3. 스켈레톤 로딩 상태 (Skeleton loading state)
출력될 이미지와 정확히 일치하는 크기의 로딩 플레이스홀더(placeholder)를 보여줍니다. 이는 이미지가 로드될 때 레이아웃 시프트 (layout shift)가 발생하는 것을 방지합니다.

{isGenerating ? (
  <div className="w-full aspect-square bg-neutral-100 
    dark:bg-neutral-800 rounded-2xl animate-pulse" />
...

종횡비 문제 (The Aspect Ratio Problem)

사용자는 1:1, 16:9, 9:16, 4:3 등 다양한 종횡비 (aspect ratios)를 선택합니다. 이미지 컨테이너는 이미지가 로드되기 전에 선택된 비율과 일치해야 하며, 그렇지 않으면 이미지가 도착했을 때 레이아웃 시프트가 발생합니다.

const aspectClasses = {
  '1:1': 'aspect-square',
  '16:9': 'aspect-video',
...

Tailwind의 임의 값 종횡비 (arbitrary aspect ratio) 문법을 사용하면 표준이 아닌 비율들도 깔끔하게 처리할 수 있습니다.

결과 요약 (Results Summary)

지표 (Metric)이전 (Before)이후 (After)
평균 파일 크기 (Average file size)~800KB PNG~180KB WebP
...

CDN 캐시 히트율 (cache hit rate)이 60%에 달했다는 점은 놀라웠습니다. 많은 사용자가 유사한 프롬프트를 생성하며, Cloudinary는 동일한 출력물의 캐시된 버전을 즉시 제공합니다.

요약 (TL;DR)

  • 동적 이미지 최적화 (Dynamic image optimization)를 위한 중간 매개체로 Cloudinary 사용
  • 승인된 도메인을 위해 next.config.js에서 remotePatterns 설정
  • LCP (Largest Contentful Paint) 이미지 요소에 priority 설정
  • 레이아웃 시프트 (Layout shift) 방지를 위해 종횡비 (Aspect ratio) 클래스를 사용하여 컨테이너 크기 사전 지정
  • 생성된 이미지의 임시 저장 — 전달 기간 종료 후 삭제

스택에 대한 전체 기술적 세부 분석은 여기에 작성된 상세 아키텍처 포스트를 참조하세요.

Next.js에서 동적 이미지 전달 (Dynamic image delivery)에 대한 귀하의 접근 방식은 무엇인가요? 댓글로 의견을 남겨주세요.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0