Next.js에서 Sharp를 활용한 이미지 최적화 구현

inguk·2024년 10월 31일
0

Next.js 프로젝트에서 Sharp를 사용해 이미지 최적화를 구현하여 성능을 향상시키는 방법을 기록합니다

목차

  • 개요
  • 디렉토리 구조
  • 설치 및 설정
  • 구현 내용
  • 사용 방법
  • 성능 개선 효과

1. 개요

이미지 최적화는 웹 성능에 큰 영향을 미치는 요소입니다. Sharp 라이브러리를 활용하여 다음과 같은 최적화를 구현했습니다:
1. WebP 포맷 변환
2. 이미지 크기 최적화
3. 품질 조정
4. 캐싱 전략
5. 프로그레시브 로딩

2. 디렉토리 구조

src/
├── app/
│   └── api/
│       └── image/
│           └── route.ts        # 이미지 최적화 API 라우트
├── lib/
│   └── image/
│       ├── optimizer.ts        # 이미지 최적화 유틸리티
│       └── constants.ts        # 이미지 관련 상수
└── components/
    └── (view)/
        └── MainCard/
            └── index.tsx       # 이미지를 사용하는 컴포넌트

3. 패키지 설치 및 설정

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

4. 구현 내용

이미지 최적화 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}`;
};

5. 컴포넌트에서 사용

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))}`}
    />
  );
};

6. 사용 방법

이미지 URL을 최적화된 URL로 변환:

const optimizedUrl = getOptimizedImageUrl(originalUrl, width, quality);

Next.js Image 컴포넌트에서 사용:

<Image
  src={optimizedUrl}
  alt="이미지 설명"
  width={width}
  height={height}
  quality={IMAGE_QUALITY}
  placeholder="blur"
  blurDataURL={blurDataUrl}
/>

개선 전

개선 후

6. 성능 개선 효과

  • 이미지 크기 감소: 70~80% 용량 감소
  • 로딩 속도 향상: WebP 포맷 사용으로 빠른 로딩
  • 프로그레시브 로딩: 사용자 경험 향상
  • 캐싱: 반복 요청 시 빠른 응답
  • 서버 부하 감소: 최적화된 이미지 캐싱
profile
Frontend
post-custom-banner

0개의 댓글