Tips & Tricks

How to Implement Lazy Loading Images: Claude Code 활용 가이드

implement lazy loading images: Claude Code 활용. 실용적인 코드 예시와 단계별 가이드를 포함합니다.

이미지지연 로딩の重要性

Web페이지の로딩速度に最も影響するのは이미지です。ファーストビュー外の이미지を지연 로딩することで、初期표시速度を大幅に改善할 수 있습니다。Claude Code를 활용하면 브라우저ネイティブの方法からカスタム구현まで、最適な이미지로딩戦略を구축할 수 있습니다。

ネイティブLazy Loadingの활용

最もシンプルな方法はHTMLのloading属性です。

> Next.jsのImage컴포넌트와 같은최적화された이미지컴포넌트を作って。
> lazy loading、プレースホルダー、반응형대응を含めて。
import { useState, useRef, useEffect } from 'react';

interface OptimizedImageProps {
  src: string;
  alt: string;
  width: number;
  height: number;
  placeholder?: 'blur' | 'skeleton' | 'none';
  blurDataURL?: string;
  sizes?: string;
  priority?: boolean;
  className?: string;
}

function OptimizedImage({
  src, alt, width, height, placeholder = 'skeleton',
  blurDataURL, sizes, priority = false, className = '',
}: OptimizedImageProps) {
  const [loaded, setLoaded] = useState(false);
  const [error, setError] = useState(false);
  const imgRef = useRef<HTMLImageElement>(null);

  // 高優先度이미지はプリロード
  useEffect(() => {
    if (priority) {
      const link = document.createElement('link');
      link.rel = 'preload';
      link.as = 'image';
      link.href = src;
      document.head.appendChild(link);
      return () => { document.head.removeChild(link); };
    }
  }, [src, priority]);

  const aspectRatio = `${width} / ${height}`;

  return (
    <div
      className={`relative overflow-hidden ${className}`}
      style={{ aspectRatio }}
    >
      {/* プレースホルダー */}
      {!loaded && placeholder === 'blur' && blurDataURL && (
        <img
          src={blurDataURL}
          alt=""
          aria-hidden="true"
          className="absolute inset-0 w-full h-full object-cover blur-lg scale-110"
        />
      )}
      {!loaded && placeholder === 'skeleton' && (
        <div className="absolute inset-0 animate-pulse bg-gray-200 dark:bg-gray-700" />
      )}

      {/* メイン画像 */}
      <img
        ref={imgRef}
        src={src}
        alt={alt}
        width={width}
        height={height}
        sizes={sizes}
        loading={priority ? 'eager' : 'lazy'}
        decoding="async"
        onLoad={() => setLoaded(true)}
        onError={() => setError(true)}
        className={`w-full h-full object-cover transition-opacity duration-300 ${
          loaded ? 'opacity-100' : 'opacity-0'
        }`}
      />

      {error && (
        <div className="absolute inset-0 flex items-center justify-center bg-gray-100 text-gray-400">
          画像を読み込めません
        </div>
      )}
    </div>
  );
}

Intersection Observer에 의한カスタム지연 로딩

function useLazyImage(threshold = 0.1) {
  const [isVisible, setIsVisible] = useState(false);
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true);
          observer.unobserve(element);
        }
      },
      { threshold, rootMargin: '200px 0px' } // 200px手前から로딩開始
    );

    observer.observe(element);
    return () => observer.disconnect();
  }, [threshold]);

  return { ref, isVisible };
}

// Usage example
function LazyImage({ src, alt, ...props }: ImgHTMLAttributes<HTMLImageElement>) {
  const { ref, isVisible } = useLazyImage();

  return (
    <div ref={ref}>
      {isVisible ? (
        <img src={src} alt={alt} {...props} />
      ) : (
        <div className="animate-pulse bg-gray-200" style={{ aspectRatio: '16/9' }} />
      )}
    </div>
  );
}

이미지フォーマットの최적화

function ResponsiveImage({ src, alt, width, height }: ImageProps) {
  const baseName = src.replace(/\.[^.]+$/, '');

  return (
    <picture>
      <source srcSet={`${baseName}.avif`} type="image/avif" />
      <source srcSet={`${baseName}.webp`} type="image/webp" />
      <img
        src={src}
        alt={alt}
        width={width}
        height={height}
        loading="lazy"
        decoding="async"
        className="w-full h-auto"
      />
    </picture>
  );
}

ぼかしプレースホルダーの생성

// 빌드時にぼかしプレースホルダーを생성
import sharp from 'sharp';

async function generateBlurDataURL(imagePath: string): Promise<string> {
  const buffer = await sharp(imagePath)
    .resize(10, 10, { fit: 'inside' })
    .blur()
    .toBuffer();

  return `data:image/png;base64,${buffer.toString('base64')}`;
}

정리

Claude Code를 활용하면 ネイティブLazy LoadingからIntersection Observer、이미지フォーマット최적화まで包括的な이미지로딩戦略を구축할 수 있습니다。スケルトン표시との연동はスケルトンローディングを、이미지処理全般は이미지処理の글를 참고하세요.

이미지の최적화手法에 대해서는web.dev - Optimize imagesが参考になります。

#Claude Code #遅延読み込み #image optimization #performance #React