우당탕탕 기술블로그

[React] 이미지 등록 후 서버 요청 시 413 에러 발생, 이미지 압축하기 본문

Web

[React] 이미지 등록 후 서버 요청 시 413 에러 발생, 이미지 압축하기

hyzzzy 2023. 7. 11. 23:50

✏️ 문제 상황

회원정보 수정 페이지 내에서 프로필에 고화질 이미지를 등록하고 수정을 서버에 요청한다면 413 Payload Too Large 에러 발생

 

여기서 HTTP 상태코드인 413 에러는 요청 엔터티가 서버에 의해 정의된 제한보다 크다는 것을 의미한다.

🧐 문제 원인

1MB 정도의 이미지 파일을 서버에 보낸다면 413 에러를 뱉어내서 수정을 할 수 없게 된다.

  const handleFileSelect = (e: ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];

    if (file) {
      const reader = new FileReader();
      reader.onload = () => {
        setImageSrc(reader.result as string);
      };
      reader.readAsDataURL(file);
    }
  }

...

  const handleSubmit = useCallback(
    async (event: MouseEvent<HTMLButtonElement>) => {
      event.preventDefault();

      if (isValid && window.confirm('수정하시겠습니까?')) {
        try {
          const formData = new FormData();

          if (imageFile) {
            formData.append('user_img', imageFile as File);
          }
          formData.append('user_name', inputName.trim());
          formData.append('user_introduction', userInfo.user_introduction);
          formData.append('user_career_goal', userInfo.user_career_goal);
          formData.append('user_stacks', JSON.stringify(stackList || []));

          await updateUserProfile(formData);

...

      <div className={styles.imageContainer}>
        <img 
          className={styles.image} 
          src={imageSrc} 
          alt={user?.user_name}
          onClick={handleImageChange}
        />
        <input
          type="file"
          accept="image/*"
          onChange={handleFileSelect}
          className={styles.fileInput}
          ref={fileInputRef}
        />
        
...

😊 해결 방안

백엔드에서 Express 를 통해 이미지의 용량 제한을 푸는 것이 일반적이겠지만 백엔드 팀원이 1분이라 빨리 프로젝트를 진행해야하는 만큼, 프로필 이미지를 페이지내에 150px로 작은 사이즈로 띄우는 만큼 간단하게 프론트 쪽에서 미리 이미지 압축기능을 처리하기로 했다. 

 

이미지 압축을 위한 라이브러리를 설치했다.

npm i browser-image-compression

우선 option 객체를 지정하여 이미지 압축에 필요한 옵션을 지정해줄 수 있다. 여기서 maxSizeMB 혹은 maxWidthOrHeight 값 중 하나는 필수로 지정해주어야 한다.

const options: Options = { 
  maxSizeMB: number,        
  maxWidthOrHeight: number, 
  onProgress: Function,         
  useWebWorker: boolean,        
  libURL: string,               
  preserveExif: boolean,        
  signal: AbortSignal,         
  maxIteration: number,         
  exifOrientation: number,      
  fileType: string,             
  initialQuality: number,      
  alwaysKeepResolution: boolean
}

압축이 필요한 이미지 파일 객체와 지정한 option을 인수로 추가해 이미지 압축을 진행하면 간단하게 이미지 압축이 완료된다.

const compressedBlob = await imageCompression(file, options);

마지막으로, form-data로 서버에 보내게 되는데 이미지를 file 객체로 보내달라고 요청해주셨다.

해당 라이브러리는 이미지 file 객체를 받아 압축을 진행하고 blob 객체를 반환하기 때문에 별도의 파일 객체 변환과정을 진행했다.

import imageCompression from 'browser-image-compression';

...

const handleFileSelect = async (e: ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];

    const options = {
      maxSizeMB: 1,
      maxWidthOrHeight: 1920,
      useWebWorker: true
    }

    if (file) {
    	try {
            // 이미지 압축
            const compressedBlob = await imageCompression(file, options);

            // Blob을 File 객체로 변환
            const convertedFile = new File([compressedBlob], file.name, {
              type: file.type,
              lastModified: Date.now()
            });

            setImageFile(convertedFile);
            const reader = new FileReader();
            reader.onload = () => {
              setImageSrc(reader.result as string);
            };

            reader.readAsDataURL(convertedFile);
          } catch (error) {
            console.log(error);
          }
    }
}

 

참고자료

 

browser-image-compression

Compress images in the browser. Latest version: 2.0.2, last published: 4 months ago. Start using browser-image-compression in your project by running `npm i browser-image-compression`. There are 120 other projects in the npm registry using browser-image-co

www.npmjs.com