troubleshooting2025-05-08·8·83/348

Node.js에서 sharp 이미지 처리 에러 해결

sharp 라이브러리를 사용한 이미지 처리 시 발생하는 다양한 에러와 그 해결 방법을 다룹니다.

Introduction

sharp는 Node.js에서 가장 많이 사용되는 이미지 처리 라이브러리입니다. 하지만 libvips 의존성 문제로 인해 various 에러가 발생할 수 있습니다. 이번 포스트에서는 sharp 관련 에러를 해결하는 방법을 살펴보겠습니다.

Environment

node --version
# v20.11.0

npm list sharp
# sharp@0.33.2

# 시스템 의존성 확인
ldconfig -p | grep vips
# libvips.so.42 => /usr/lib/x86_64-linux-gnu/libvips.so.42

Problem

sharp를 설치하거나 사용할 때 다음과 같은 에러가 발생했습니다:

# 설치 에러
npm install sharp
# sharp: Installation error: sharp requires libvips >= 8.14.0

# 런타임 에러
Error: Could not load the "sharp" module using the linux-x64 runtime
libvips.so.42: cannot open shared object file: No such file or directory

추가 에러들

# Windows 환경
sharp: Installation error: The sharp library requires the "node-gyp" build tools

# 메모리 에러
Error: vips_jpegload: unable to read image data

# 포맷 지원 에러
Error: Input buffer contains unsupported image format

Solution

1. Windows에서 sharp 설치

# 방법 1: windows-build-tools 설치
npm install --global windows-build-tools

# 방법 2: Visual Studio Build Tools 설치 후
npm install sharp --build-from-source

# 방법 3: 바이너리 버전 사용
npm install sharp --arch=x64 --platform=win32

2. 기본 이미지 처리

const sharp = require('sharp');

// 이미지 리사이즈
async function resizeImage(inputPath, outputPath, width, height) {
    try {
        await sharp(inputPath)
            .resize(width, height, {
                fit: 'cover',
                position: 'centre',
            })
            .toFile(outputPath);

        console.log('Image resized successfully');
    } catch (error) {
        console.error('Resize error:', error.message);
        throw error;
    }
}

// 이미지 포맷 변환
async function convertFormat(inputPath, outputPath, format) {
    const options = {
        jpeg: { quality: 80, progressive: true },
        png: { compressionLevel: 6, adaptiveFiltering: true },
        webp: { quality: 80, lossless: false },
        avif: { quality: 80, speed: 4 },
    };

    await sharp(inputPath)
        .toFormat(format, options[format])
        .toFile(outputPath);
}

3. 에러 핸들링 강화

const sharp = require('sharp');

class ImageProcessor {
    constructor() {
        this.maxSize = 10 * 1024 * 1024; // 10MB
        this.allowedFormats = ['jpeg', 'jpg', 'png', 'webp', 'gif', 'avif'];
    }

    async processImage(inputPath, outputPath, options = {}) {
        try {
            // 메타데이터 확인
            const metadata = await sharp(inputPath).metadata();

            if (!this.allowedFormats.includes(metadata.format)) {
                throw new Error(`Unsupported format: ${metadata.format}`);
            }

            if (metadata.size > this.maxSize) {
                throw new Error('Image too large');
            }

            // 처리 파이프라인
            let pipeline = sharp(inputPath);

            // 리사이즈 옵션
            if (options.width || options.height) {
                pipeline = pipeline.resize({
                    width: options.width,
                    height: options.height,
                    fit: options.fit || 'cover',
                    withoutEnlargement: true,
                });
            }

            // 포맷 변환
            if (options.format) {
                pipeline = pipeline.toFormat(options.format, {
                    quality: options.quality || 80,
                });
            }

            // 메타데이터 유지
            if (options.keepMetadata) {
                pipeline = pipeline.withMetadata();
            }

            // 출력
            await pipeline.toFile(outputPath);

            return {
                success: true,
                metadata: await sharp(outputPath).metadata(),
            };
        } catch (error) {
            return {
                success: false,
                error: error.message,
                code: error.code,
            };
        }
    }
}

// 사용 예시
const processor = new ImageProcessor();
const result = await processor.processImage(
    'input.jpg',
    'output.webp',
    { width: 800, height: 600, format: 'webp', quality: 80 }
);

4. 스트리밍 처리

const sharp = require('sharp');
const { pipeline } = require('stream');
const fs = require('fs');

async function processImageStream(inputPath, outputPath) {
    return new Promise((resolve, reject) => {
        const readStream = fs.createReadStream(inputPath);
        const transform = sharp()
            .resize(800, 600)
            .webp({ quality: 80 });
        const writeStream = fs.createWriteStream(outputPath);

        pipeline(readStream, transform, writeStream, (err) => {
            if (err) {
                reject(err);
            } else {
                resolve();
            }
        });
    });
}

5. 배치 처리

const sharp = require('sharp');
const path = require('path');
const fs = require('fs').promises;

class BatchProcessor {
    constructor(inputDir, outputDir) {
        this.inputDir = inputDir;
        this.outputDir = outputDir;
        this.concurrency = 4;
    }

    async processAll() {
        const files = await fs.readdir(this.inputDir);
        const imageFiles = files.filter(f =>
            /\.(jpg|jpeg|png|webp|gif)$/i.test(f)
        );

        const chunks = this.chunkArray(imageFiles, this.concurrency);
        const results = [];

        for (const chunk of chunks) {
            const chunkResults = await Promise.all(
                chunk.map(file => this.processFile(file))
            );
            results.push(...chunkResults);
        }

        return results;
    }

    async processFile(filename) {
        const inputPath = path.join(this.inputDir, filename);
        const outputPath = path.join(this.outputDir, `processed_${filename}`);

        try {
            await sharp(inputPath)
                .resize(1200, 1200, { fit: 'inside', withoutEnlargement: true })
                .jpeg({ quality: 85, progressive: true })
                .toFile(outputPath);

            return { file: filename, success: true };
        } catch (error) {
            return { file: filename, success: false, error: error.message };
        }
    }

    chunkArray(array, size) {
        const chunks = [];
        for (let i = 0; i < array.length; i += size) {
            chunks.push(array.slice(i, i + size));
        }
        return chunks;
    }
}

// 사용 예시
const processor = new BatchProcessor('./uploads', './processed');
const results = await processor.processAll();
console.log(`Processed ${results.filter(r => r.success).length}/${results.length} files`);

Lessons Learned

  1. 시스템 의존성 확인: sharp 설치 전 libvips 등 시스템 의존성을 확인하세요
  2. 에러 핸들링: 모든 이미지 처리 작업에 적절한 에러 핸들링을 추가하세요
  3. 메타데이터 검증: 처리 전 이미지 메타데이터를 확인하여 유효성을 검증하세요
  4. 배치 처리 최적화: 대량 이미지 처리 시 동시성을 제한하여 리소스를 관리하세요
  5. 포맷 지원 확인: 처리할 이미지 포맷이 sharp에서 지원되는지 확인하세요

This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.