Home Jekyll 블로그 이미지 자동 최적화 시스템 구축
Post
Cancel

Jekyll 블로그 이미지 자동 최적화 시스템 구축

Jekyll 블로그 이미지 자동 최적화


개요

  • Jekyll 기반 블로그에서 이미지 자동 최적화 시스템을 구축하여 웹 성능을 개선한 과정을 소개합니다.

문제 상황 분석

초기 성능 문제

  • 기술 블로그에서 측정된 성능 지표
    • 이미지 용량
      • 149MB
    • 페이지 로딩 시간
      • 3-5초 (이미지 중심 페이지)
    • 이미지 파일 수
      • 1,000개 이상

이미지 현황 파악

1
2
3
4
5
6
7
# 이미지 디렉토리 용량 분석
du -sh assets/img
# 결과: 149M	assets/img

# 이미지 파일 개수 확인
find assets/img -type f \( -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" \) | wc -l
# 결과: 1,000개 이상의 이미지 파일
  • 이미지 파일 특성
    • 파일 형식
      • PNG, JPEG
    • 압축 상태
      • 압축되지 않은 원본 파일
    • 파일 수
      • 1,000개 이상의 이미지 파일

Core Web Vitals 측정

  • Core Web Vitals 지표
    • Google이 정의한 웹사이트 사용자 경험을 측정하는 핵심 지표

    • 측정 방법
      • 측정 도구
        • Chrome DevTools, Lighthouse
      • 측정 환경
        • 로컬 Jekyll 서버 (localhost:4000)
      1
      2
      3
      
        # Lighthouse CLI를 사용한 성능 측정
        npm install -g lighthouse
        lighthouse http://localhost:4000 --output=json --output-path=./lighthouse-report.json
      
    • 측정된 Core Web Vitals 지표

      • LCP (Largest Contentful Paint): 3.2초
      • 페이지의 가장 큰 콘텐츠 요소가 화면에 렌더링되는 시간
      • 좋은 점수: 2.5초 이하, 개선 필요: 4.0초 초과
      • 이미지가 가장 큰 콘텐츠인 경우 로딩 시간에 직접 영향

      • CLS (Cumulative Layout Shift): 0.15
      • 페이지 로딩 중 예상치 못한 레이아웃 변화의 정도
      • 좋은 점수: 0.1 이하, 개선 필요: 0.25 초과
      • 이미지 로딩 시 크기 변화로 인한 레이아웃 시프트 발생

      • FID (First Input Delay): 180ms
      • 사용자가 페이지와 처음 상호작용할 때의 응답 시간
      • 좋은 점수: 100ms 이하, 개선 필요: 300ms 초과
      • 대용량 이미지 로딩으로 인한 메인 스레드 블로킹 영향

자동화 시스템 구성

이미지 최적화 도구 설치

1
2
3
4
5
# 이미지 최적화 도구 설치
brew install pngquant jpegoptim

# 설치 확인
which pngquant && which jpegoptim

자동 최적화 스크립트 생성

  • 파일 생성 위치
    • 프로젝트 루트 디렉토리에 auto-optimize-images.js 파일 생성
    • 실행 권한 부여
      1
      
        chmod +x auto-optimize-images.js
      
  • auto-optimize-images.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    
      #!/usr/bin/env node
    
      const fs = require('fs');
      const path = require('path');
      const { execSync } = require('child_process');
    
      // 설정
      const IMG_DIR = 'assets/img';
      const QUALITY = 85;
      const SKIP_FAVICONS = true;
    
      // 이미지 최적화 함수
      function optimizeImage(filePath) {
          const ext = path.extname(filePath).toLowerCase();
            
          try {
              if (ext === '.png') {
                  execSync(`pngquant --force --ext .png --quality=65-${QUALITY} "${filePath}"`, { stdio: 'ignore' });
              } else if (ext === '.jpg' || ext === '.jpeg') {
                  execSync(`jpegoptim --max=${QUALITY} --strip-all "${filePath}"`, { stdio: 'ignore' });
              }
              return true;
          } catch (error) {
              return false;
          }
      }
    
      // 새로 추가된 이미지 파일 찾기
      function getNewImages() {
          try {
              const addedFiles = execSync('git diff --cached --name-status', { encoding: 'utf8' });
              const newImages = [];
              const lines = addedFiles.split('\n');
                
              lines.forEach(line => {
                  if (line.startsWith('A\t') && line.includes(IMG_DIR)) {
                      const filePath = line.substring(2);
                      const ext = path.extname(filePath).toLowerCase();
                      if (['.png', '.jpg', '.jpeg'].includes(ext)) {
                          if (!SKIP_FAVICONS || !filePath.includes('favicon')) {
                              newImages.push(filePath);
                          }
                      }
                  }
              });
                
              return newImages;
          } catch (error) {
              return [];
          }
      }
    
      // 메인 실행
      function main() {
          console.log('이미지 자동 최적화 시작...');
            
          const newImages = getNewImages();
            
          if (newImages.length === 0) {
              console.log('최적화할 새 이미지가 없습니다.');
              return;
          }
            
          console.log(`${newImages.length}개의 새 이미지를 발견했습니다.`);
            
          let optimized = 0;
          let failed = 0;
            
          newImages.forEach(imagePath => {
              console.log(`최적화 중: ${imagePath}`);
                
              if (optimizeImage(imagePath)) {
                  optimized++;
                  console.log(`완료: ${imagePath}`);
              } else {
                  failed++;
                  console.log(`실패: ${imagePath}`);
              }
          });
            
          console.log(`최적화 완료: ${optimized}개 성공, ${failed}개 실패`);
            
          if (optimized > 0) {
              console.log('최적화된 파일들을 Git에 다시 추가합니다...');
              try {
                  execSync(`git add ${newImages.join(' ')}`, { stdio: 'ignore' });
                  console.log('Git에 추가 완료!');
              } catch (error) {
                  console.log('Git 추가 실패:', error.message);
              }
          }
      }
    
      main();
    

Git 훅 설정

  • 파일 생성 위치
    • .git/hooks/pre-commit 파일 생성
    • 실행 권한 부여
      1
      
        chmod +x .git/hooks/pre-commit
      
  • pre-commit 훅

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
      #!/bin/bash
    
      # 자동 이미지 최적화 Git 훅
      echo "이미지 최적화를 확인합니다..."
    
      # Node.js가 설치되어 있는지 확인
      if ! command -v node &> /dev/null; then
          echo "Node.js가 설치되어 있지 않습니다. 이미지 최적화를 건너뜁니다."
          exit 0
      fi
    
      # 이미지 최적화 도구가 설치되어 있는지 확인
      if ! command -v pngquant &> /dev/null || ! command -v jpegoptim &> /dev/null; then
          echo "이미지 최적화 도구가 설치되어 있지 않습니다."
          echo "다음 명령어로 설치하세요: brew install pngquant jpegoptim"
          exit 0
      fi
    
      # 자동 최적화 스크립트 실행
      node auto-optimize-images.js
    
      echo "이미지 최적화 완료!"
    

실시간 파일 감시 스크립트

  • 파일 생성 위치
    • 프로젝트 루트 디렉토리에 watch-images.js 파일 생성
    • 실행 권한 부여
      1
      
        chmod +x watch-images.js
      
  • watch-images.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    
      #!/usr/bin/env node
    
      const fs = require('fs');
      const path = require('path');
      const { execSync } = require('child_process');
    
      // 설정
      const IMG_DIR = 'assets/img';
      const QUALITY = 85;
    
      // 이미지 최적화 함수
      function optimizeImage(filePath) {
          const ext = path.extname(filePath).toLowerCase();
            
          try {
              if (ext === '.png') {
                  execSync(`pngquant --force --ext .png --quality=65-${QUALITY} "${filePath}"`, { stdio: 'ignore' });
              } else if (ext === '.jpg' || ext === '.jpeg') {
                  execSync(`jpegoptim --max=${QUALITY} --strip-all "${filePath}"`, { stdio: 'ignore' });
              }
              return true;
          } catch (error) {
              return false;
          }
      }
    
      // 파일 감시 시작
      function startWatching() {
          console.log('이미지 파일 감시를 시작합니다...');
          console.log('감시 디렉토리:', IMG_DIR);
          console.log('품질 설정:', QUALITY + '%');
          console.log('종료하려면 Ctrl+C를 누르세요\n');
            
          fs.watch(IMG_DIR, { recursive: true }, (eventType, filename) => {
              if (eventType === 'rename' && filename) {
                  const filePath = path.join(IMG_DIR, filename);
                  const ext = path.extname(filename).toLowerCase();
                    
                  if (['.png', '.jpg', '.jpeg'].includes(ext)) {
                      setTimeout(() => {
                          if (fs.existsSync(filePath)) {
                              console.log(`새 이미지 발견: ${filename}`);
                                
                              if (optimizeImage(filePath)) {
                                  console.log(`최적화 완료: ${filename}`);
                              } else {
                                  console.log(`최적화 실패: ${filename}`);
                              }
                          }
                      }, 1000);
                  }
              }
          });
      }
    
      startWatching();
    

사용 방법

Git 훅 방식

1
2
3
4
# 이미지 추가 후 커밋하면 자동으로 최적화됨
git add assets/img/new-image.png
git commit -m "새 이미지 추가"
# 자동으로 최적화 후 커밋됨

수동 실행

1
2
# 새로 추가된 이미지만 최적화
npm run auto-optimize

실시간 감시

1
2
# 터미널에서 실행하면 이미지 추가 시 실시간 최적화
npm run watch-images

package.json 설정

  • 파일 생성 위치
    • 프로젝트 루트 디렉토리의 package.json 파일에 scripts 섹션 추가
    • 기존 scripts 섹션이 있다면 해당 내용을 추가
  • package.json
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      {
      "scripts": {
          "build": "gulp build",
          "dev": "gulp dev",
          "optimize-images": "node optimize-images-safe.js",
          "auto-optimize": "node auto-optimize-images.js",
          "watch-images": "node watch-images.js",
          "test": "echo 'Testing blog functionality...'"
      }
      }
    

성능 최적화 결과

최적화 전후 비교

  • 용량 최적화 결과

    1
    2
    3
    4
    5
    6
    7
    
      # 최적화 전
      du -sh assets/img
      # 결과: 149M	assets/img
    
      # 최적화 후
      du -sh assets/img
      # 결과: 45M	assets/img
    
  • 페이지 로딩 시간 개선 (Chrome DevTools 기준)

    페이지 유형최적화 전최적화 후개선율
    이미지 중심 포스트3-5초1-2초60-70%
    일반 포스트2-3초0.8-1.5초50-60%
    메인 페이지4-6초1.5-2.5초60-70%

Core Web Vitals 개선 효과

  • Lighthouse 성능 점수
    • 최적화 전
      • Performance: 65-70점
      • LCP: 3.2초
      • CLS: 0.15
      • FID: 180ms
    • 최적화 후
      • Performance: 85-90점
      • LCP: 1.8초 (44% 개선)
      • CLS: 0.05 (67% 개선)
      • FID: 95ms (47% 개선)

최적화 결과 요약

핵심 성과

항목최적화 전최적화 후개선율
이미지 용량149MB45MB70% 감소
페이지 로딩 시간3-5초1-2초60-70% 단축
Lighthouse 점수65-70점85-90점20-25점 향상
LCP3.2초1.8초44% 개선
CLS0.150.0567% 개선
FID180ms95ms47% 개선

적용된 기술

  • 이미지 압축
    • pngquant, jpegoptim
  • 자동화
    • Git pre-commit 훅
  • 모니터링
    • Lighthouse, Chrome DevTools
  • 스크립트
    • Node.js 기반 최적화 도구

트러블슈팅

자주 발생하는 문제와 해결 방법

  • 이미지 최적화 도구 설치 오류
    • 문제
      • pngquant 또는 jpegoptim 명령어를 찾을 수 없음
      1
      2
      3
      
        # 오류 메시지
        command not found: pngquant
        command not found: jpegoptim
      
    • 해결 방법

      1
      2
      3
      4
      5
      
        # macOS
        brew install pngquant jpegoptim
      
        # Ubuntu/Debian
        sudo apt-get install pngquant jpegoptim
      
  • Git 훅 실행 권한 문제
    • 문제
      • pre-commit 훅이 실행되지 않음
      1
      2
      
        # 오류 메시지
        .git/hooks/pre-commit: Permission denied
      
    • 해결 방법

      1
      2
      3
      4
      5
      
        # 실행 권한 부여
        chmod +x .git/hooks/pre-commit
      
        # 권한 확인
        ls -la .git/hooks/pre-commit
      
  • 이미지 최적화 실패
    • 문제: 일부 이미지 파일이 최적화되지 않음

    • 원인 분석
      • 손상된 이미지 파일
      • 권한 문제
      • 디스크 공간 부족
    • 해결 방법
      1
      2
      3
      4
      5
      6
      7
      8
      9
      
        # 이미지 파일 무결성 확인
        file assets/img/problematic-image.png
      
        # 권한 확인 및 수정
        chmod 644 assets/img/*.png
        chmod 644 assets/img/*.jpg
      
        # 디스크 공간 확인
        df -h
      
  • Node.js 버전 호환성 문제
    • 문제
      • 스크립트 실행 시 Node.js 버전 오류
    • 해결 방법
      1
      2
      3
      4
      5
      6
      
        # Node.js 버전 확인
        node --version
      
        # nvm을 사용한 버전 관리
        nvm install 16
        nvm use 16
      
  • Git 훅이 실행되지 않는 문제
    • 문제
      • pre-commit 훅을 설정했지만 이미지 최적화가 실행되지 않음
    • 원인 분석
      • Git 훅이 비활성화되어 있음
      • 훅 파일이 올바른 위치에 없음
      • Git 설정에서 훅이 무시됨
    • 해결 방법
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
        # Git 훅 활성화 확인
        git config core.hooksPath
      
        # 훅 파일 위치 확인
        ls -la .git/hooks/pre-commit
      
        # Git 훅 경로 설정 (필요시)
        git config core.hooksPath .git/hooks
      
        # 훅 테스트
        git add assets/img/test-image.png
        git commit -m "훅 테스트"
      
  • 이미지 품질 최적화 문제
    • 문제
      • 최적화 후 이미지 품질이 너무 낮아짐
      • 일부 이미지에서 아티팩트 발생
    • 원인 분석
      • 압축 품질 설정이 너무 낮음 (85%)
      • 이미지 유형에 따른 최적화 설정 부족
      • 원본 이미지 품질 문제
    • 해결 방법
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      
        # 품질 설정 조정 (auto-optimize-images.js 수정)
        const QUALITY = 90; // 85에서 90으로 상향 조정
      
        # 이미지 유형별 품질 설정
        if (ext === '.png') {
            // PNG는 더 높은 품질 유지
            execSync(`pngquant --quality=80-95 "${filePath}"`);
        } else if (ext === '.jpg' || ext === '.jpeg') {
            // JPEG는 적당한 품질
            execSync(`jpegoptim --max=90 "${filePath}"`);
        }
      
        # 원본 이미지 품질 확인
        identify -verbose assets/img/sample-image.jpg
      

Reference

Contents

[컴퓨터과학 개론] 3강 - 자료 구조

-