목차
- 개요
- 디렉토리 구조
- 설치 및 설정
- 구현 내용
- 사용 방법
- 성능 개선 효과
이미지 최적화는 웹 성능에 큰 영향을 미치는 요소입니다. Sharp 라이브러리를 활용하여 다음과 같은 최적화를 구현했습니다:
1. WebP 포맷 변환
2. 이미지 크기 최적화
3. 품질 조정
4. 캐싱 전략
5. 프로그레시브 로딩
src/
├── app/
│ └── api/
│ └── image/
│ └── route.ts # 이미지 최적화 API 라우트
├── lib/
│ └── image/
│ ├── optimizer.ts # 이미지 최적화 유틸리티
│ └── constants.ts # 이미지 관련 상수
└── components/
└── (view)/
└── MainCard/
└── index.tsx # 이미지를 사용하는 컴포넌트
npm install sharp
//next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
formats: ['image/webp', 'image/avif'], // AVIF 포맷도 지원
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
minimumCacheTTL: 60,
dangerouslyAllowSVG: true,
contentSecurityPolicy:
"default-src 'self'; script-src 'none'; sandbox;",
domains: [
'product-images.kr.object.ncloudstorage.com',
'image.msscdn.net'
],
formats: ['image/webp'],
minimumCacheTTL: 60,
},
experimental: {
serverActions: true,
},
}
module.exports = nextConfig
이미지 최적화 API 라우트
import { NextRequest, NextResponse } from 'next/server';
import sharp from 'sharp';
export const runtime = 'nodejs';
export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams;
const imageUrl = searchParams.get('url');
const width = parseInt(searchParams.get('width') || '800');
const quality = parseInt(searchParams.get('quality') || '80');
if (!imageUrl) {
return new NextResponse('Image URL is required', { status: 400 });
}
const response = await fetch(imageUrl);
const buffer = await response.arrayBuffer();
const optimizedImage = await sharp(Buffer.from(buffer))
.resize(width, null, {
withoutEnlargement: true,
fit: 'inside',
})
.webp({ quality })
.toBuffer();
return new NextResponse(optimizedImage, {
headers: {
'Content-Type': 'image/webp',
'Cache-Control': 'public, max-age=31536000, immutable',
},
});
} catch (error) {
console.error('Image optimization error:', error);
return new NextResponse('Image optimization failed', { status: 500 });
}
}
//constants.ts
export const IMAGE_QUALITY = 80;
export const DEFAULT_WIDTH = 800;
export const THUMBNAIL_WIDTH = 172;
//optimizer.ts
import { IMAGE_QUALITY, DEFAULT_WIDTH } from './constants';
export const getOptimizedImageUrl = (
originalUrl: string,
width = DEFAULT_WIDTH,
quality = IMAGE_QUALITY
) => {
if (!originalUrl) return '';
if (originalUrl.startsWith('https://')) {
return originalUrl;
}
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || '';
const encodedUrl = encodeURIComponent(originalUrl);
return `${baseUrl}/api/image?url=${encodedUrl}&width=${width}&quality=${quality}`;
};
import { getOptimizedImageUrl } from '@/lib/image/optimizer';
import { THUMBNAIL_WIDTH, IMAGE_QUALITY } from '@/lib/image/constants';
const MainCard: React.FC<MainCardProps> = ({ mainImg, ...props }) => {
const optimizedImageUrl = getOptimizedImageUrl(
mainImg,
THUMBNAIL_WIDTH,
IMAGE_QUALITY
);
return (
<Image
src={optimizedImageUrl}
alt={props.title}
width={THUMBNAIL_WIDTH}
height={180}
quality={IMAGE_QUALITY}
placeholder="blur"
blurDataURL={`data:image/svg+xml;base64,${toBase64(shimmer(172, 180))}`}
/>
);
};
이미지 URL을 최적화된 URL로 변환:
const optimizedUrl = getOptimizedImageUrl(originalUrl, width, quality);
<Image
src={optimizedUrl}
alt="이미지 설명"
width={width}
height={height}
quality={IMAGE_QUALITY}
placeholder="blur"
blurDataURL={blurDataUrl}
/>