Firebase Storage 이미지 업로드 사용법: 상품 이미지 관리 흐름 만들기

2026.01.22·수정 2026.09.13·약 8분·작성: 해비·블로그 소개

학습 목표와 사전 지식

목표: 상품 이미지 업로드의 경로·파일 검사·DB 반영·실패 복구 책임을 분리합니다.
사전 지식: 브라우저 File, Promise, Firebase Auth와 Storage 초기화

이미지 업로드 성공과 상품 DB 저장 성공은 별개의 작업입니다. 객체 경로를 업로드 결과에서 저장하고, 부분 실패를 처리해야 재시도 때 고아 파일이나 중복 이미지가 쌓이지 않습니다.

학습 예제의 경로 계약

아래 모듈은 판매자 본인 경로 users/{uid}/products/{productId}/{fileId}에 이미지 한 개를 올립니다. 기존 카테고리 기반 URL 정규식에서 상품 ID를 역추출하는 대신 storagePath를 DB 필드로 별도 보관합니다. 상품 편집 권한 자체는 별도 Firestore Rules 또는 서버에서 확인해야 합니다. 경로에 본인 UID를 썼다는 것만으로 특정 상품의 소유권이 증명되지는 않습니다.

src/product-images.js · 전체 업로드 모듈; 초기화된 storage·인증 user·File을 전달

import { deleteObject, getDownloadURL, ref, uploadBytes } from 'firebase/storage';

export async function uploadProductImage(storage, user, productId, file) {
  if (!user) throw new Error('로그인이 필요합니다.');
  if (!/^[a-zA-Z0-9_-]+$/.test(productId)) throw new Error('상품 ID 오류');
  if (!['image/jpeg', 'image/png', 'image/webp'].includes(file.type)) {
    throw new Error('JPEG, PNG, WebP 파일을 선택하세요.');
  }
  if (file.size === 0 || file.size > 5 * 1024 * 1024) {
    throw new Error('0바이트보다 크고 5 MiB 이하인 파일이 필요합니다.');
  }
  const path = `users/${user.uid}/products/${productId}/${crypto.randomUUID()}`;
  const snapshot = await uploadBytes(ref(storage, path), file, { contentType: file.type });
  try {
    return { storagePath: snapshot.ref.fullPath, url: await getDownloadURL(snapshot.ref) };
  } catch (error) {
    const cleanup = await Promise.allSettled([deleteObject(snapshot.ref)]);
    if (cleanup[0].status === 'rejected') {
      throw new AggregateError([error, cleanup[0].reason], 'URL 조회와 객체 정리에 실패했습니다.');
    }
    throw error;
  }
}

storage.rules · 전체 최소 규칙; 다른 기존 경로는 병합 시 보존

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/products/{productId}/{fileId} {
      function owner() {
        return request.auth != null && request.auth.uid == userId;
      }
      function image() {
        return request.resource.size > 0
          && request.resource.size <= 5 * 1024 * 1024
          && request.resource.contentType.matches('image/(jpeg|png|webp)');
      }
      allow get: if owner();
      allow create, update: if owner() && image();
      allow delete: if owner();
    }
  }
}
Firebase Storage 이미지 업로드의 파일 검증 경로 생성 업로드 URL 저장 흐름

프론트 검사와 Rules의 역할

브라우저 file.type과 contentType은 파일 내용을 실제로 해독한 결과가 아닙니다. 둘 다 허용 MIME·용량 계약에 도움을 주지만 악성 파일의 실내용 검사를 대체하지 못합니다. 실제 이미지 포맷 검증이 필요하면 서버에서 디코딩하세요. 5 × 1024 × 1024바이트는 5 MiB입니다. SVG는 이 예제의 허용 목록에 포함하지 않습니다.

상품 데이터까지 저장할 때

업로드 반환값의 storagePath와 url을 상품 DB에 반영합니다. DB 저장이 실패하면 해당 신규 객체 삭제를 시도하고 정리 실패도 재시도 대상으로 기록합니다. 기존 이미지 교체는 새 업로드 → DB에 새 경로 반영 → 이전 객체 삭제 순으로 진행합니다. 여러 파일의 Promise.all 실패는 이미 성공한 업로드를 자동으로 취소·롤백하지 않습니다. 동시 수를 제한하고 개별 결과를 수집하세요.

Firebase Storage 상품 이미지 관리의 검증 삭제 경로 파싱 URL 해제 체크 구조

미리보기와 다운로드 링크의 수명

업로드 전에는 URL.createObjectURL(file)로 로컬 미리보기를 만들고 교체·컴포넌트 종료 때 URL.revokeObjectURL로 해제합니다. 이는 Firebase URL이 아닙니다. getDownloadURL의 token URL은 가진 사람이 접근 가능한 공유 링크로 취급합니다. 엄격한 비공개 파일은 token URL을 공개하지 않고 인증 SDK 다운로드나 서버 전달 정책을 설계하세요.

실제 프로젝트에서 확인할 것

로그인한 A의 정상 JPEG, 0바이트, 5 MiB 초과, 허용하지 않는 MIME을 구분합니다. B로 A 경로에 업로드·삭제를 시도하면 거부되어야 합니다. 업로드 후 DB 저장 실패를 의도적으로 만들어 신규 파일 정리 여부를 확인합니다. Storage 플랜·bucket은 Console 실제 상태를 확인해야 하며 이 모듈만으로 로그인 UI나 상품 관리 앱이 완성되지는 않습니다.

직접 확인할 결과

업로드 모듈 문법 검사
MIME·0/상한 용량·권한 표 검토
실제 업로드·정리·브라우저 File 미실행

사용 전 플랜 확인

2026-09-13 공식 문서 기준 Cloud Storage for Firebase는 기본 버킷 접근을 포함해 Blaze 종량제 플랜이 필요합니다. 무료 사용량과 결제 플랜은 다른 개념입니다. 개발 프로젝트의 결제 연결·예산 알림·실제 사용량을 확인하세요.

검증 범위와 기준일

공식 문서 확인일: 2026-09-13. 웹 SDK 예제는 모듈형 API 기준입니다. 이 글의 코드·구조 정적 검토와 실제 클라우드 인증·저장·배포 검증을 구분합니다. 본인 개발 Firebase 프로젝트의 실제 인증·권한·배포 요청은 이 편집 환경에서 실행하지 않았습니다.

이어서 학습하기

공식 문서

이 글이 도움이 되었나요?

조회 중

Firebase 학습 순서

필수 13개 · 전체 19개

읽음 기록 관리

전체 과정 목차 (19개)
  1. 필수 길잡이 · Firebase 실무 로드맵: Auth, Firestore, Storage, Functions
  2. 필수 학습 · Firebase 초기화 구조: Next.js firebase.ts 설계 기준
  3. 필수 학습 · Firebase Authentication 사용 기준: 로그인 유지와 비밀번호 재설정
  4. 필수 학습 · Firebase 보안 규칙 설계: Firestore와 Storage 권한 관리하기
  5. 필수 학습 · Firestore CRUD 사용법: 컬렉션 구조와 읽기 쓰기 흐름
  6. 필수 학습 · Firebase Auth Context 설계: 로그인 권한과 라우팅 관리하기
  7. 필수 학습 · Firebase Storage 이미지 업로드 사용법: 상품 이미지 관리 흐름 만들기 현재 글
  8. 필수 학습 · Firebase Custom Claims 사용법: 관리자 권한 구분하기
  9. 필수 학습 · Firebase Functions v2 사용법: 트리거 배포 Secret 처리
  10. 필수 학습 · Firestore seed data 설계: 리뷰 더미 데이터 구조 잡기
  11. 선택 참고 · Firebase 배포 제외 파일 설정: firebase.json의 ignore 사용법
  12. 선택 참고 · Firebase Firestore 인덱스 삭제 질문 해결: 배포 중 안전하게 판단하기
  13. 선택 참고 · Firebase Auth unauthorized-domain 오류 해결: 로그인 도메인 설정 확인
  14. 선택 참고 · Firebase permission-denied 오류 해결: Firestore Rules 체크리스트
  15. 선택 참고 · Firebase Storage 이미지 오류 해결: 403·404·token·Rules 확인법
  16. 선택 참고 · Firebase CORS 오류 해결: Storage 이미지 업로드가 막힐 때 확인할 설정
  17. 필수 선수 · Firebase 실무 오류 해결 모음: Storage, Firestore, Auth 체크리스트
  18. 필수 학습 · Firestore undefined 오류 해결: 선택 필드가 저장을 막을 때
  19. 필수 학습 · Firestore arrayUnion 중첩 배열 오류: 배열을 그대로 넘기면 실패하는 이유

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기