Debugging/2026.05.03/7 min read

이미지 업로드 전에 서버에서 WebP로 정리하기

프로필, 배너, 분석 사진 업로드가 커지고 느려질 때, 서버 업로드 전 이미지 검증과 압축 계층을 만든 기록입니다.

UploadImageSharp

Code notes

코드에서 확인한 구현 포인트

배포 기준 2026.05.03

관련 파일

apps/web/lib/server/image-upload.tsapps/web/app/api/profile/photo/route.tsapps/web/app/api/admin/*/upload/route.ts

구현 메모

prepareImageUpload는 이미지 MIME 타입과 확장자를 확인한 뒤 sharp로 회전 보정, 리사이즈, WebP 변환을 수행한다.

압축 실패 시 요청 전체를 실패시키지 않고 원본 buffer와 content type으로 업로드를 계속할 수 있게 했다.

프로필 사진 업로드는 안전한 파일명, Storage 업로드, profiles upsert, user metadata 갱신을 한 흐름으로 처리한다.

업로드 성공만으로는 충분하지 않았다

초기 업로드는 파일을 받아 Storage에 올리는 데 집중했다. 하지만 운영 화면과 모바일 화면이 늘어나면서 이미지 크기, 파일명, 확장자, content type이 조금씩 문제를 만들기 시작했다.

큰 이미지는 업로드와 표시를 느리게 만들고, 파일명이 불안정하면 저장 경로가 지저분해진다. 그래서 업로드 전에 서버에서 이미지를 한 번 정리하는 공통 계층이 필요했다.

이미지인지 먼저 판단하기

prepareImageUpload는 content type과 파일 확장자를 보고 이미지 파일인지 판단한다. 이미지라면 회전 정보를 반영하고, 최대 크기를 제한한 뒤 WebP로 변환한다.

이미지가 아니거나 변환에 실패하면 무조건 요청을 죽이지 않고 원본 buffer와 content type을 돌려준다. 업로드 계층이 너무 공격적으로 실패하면 운영자가 단순 파일 교체도 못 하게 될 수 있기 때문이다.

프로필 사진 업로드에 적용하다

프로필 사진 API에서는 multipart/form-data인지, file과 userId가 있는지 먼저 확인한다. 그 다음 안전한 파일명으로 바꾸고, 압축 결과의 확장자에 맞춰 Storage 경로를 만든다.

업로드 후에는 public URL을 profiles와 사용자 metadata에 반영한다. 파일 저장과 사용자 프로필 갱신이 함께 성공해야 앱에서 바로 새 사진을 볼 수 있다.

다시 배운 점

이미지 업로드는 Storage 기능이 아니라 제품 성능 기능이다. 압축, 파일명 정리, content type, DB 갱신을 한 흐름으로 봐야 한다.

또 압축 실패가 곧 업로드 실패는 아니다. 사용자와 운영자에게 필요한 것은 가능한 한 안전하게 저장하고, 실패 지점을 분명히 알려주는 것이다.

Keep reading

다른 글 이어서 보기

전체 글 보기