일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Boostrap
- url
- JavaScript
- db
- this
- Protocol
- nosql
- til
- jQuery
- firestoredatabase
- Cloud
- HTML
- 배포
- CSS
- Database
- REACT
- Github Pages
- IntersectionObserver
- W
- data
- Fetch
- supabase
- http
- useEffect
- API
- github
- bootstrap
- web
- TMDB
- SQL
- Today
- Total
072DATA
다중으로 이미지 업로드 구현하기 ( supabase Storage ) 본문
개발을 하다 보면 이미지 업로드 기능이 필요한 경우가 많은데,
특히 여러 장의 이미지를 한 번에 처리해야 하는 경우가 있습니다.
오늘은 제가 구현했던 다중 이미지 업로드 기능을 기록해보려고 합니다.
구현 목표
- 최대 5장까지 이미지 업로드 가능
- 이미지 형식 및 크기 제한 (JPG, PNG, GIF, WEBP / 5MB 이하)
- 이미지 미리보기 기능
- 개별 이미지 삭제 기능
1. 이미지 업로드 컴포넌트 만들기
먼저 이미지 업로드를 위한 기본적인 UI를 구성했고
이 코드에서 ImageUpload 컴포넌트는 다섯 개의 이미지 미리보기 자리를 제공하는 간단한 레이아웃을 만들어유
const ImageUpload = () => {
// 파일 input 참조를 위한 ref
const fileInputRef = useRef<HTMLInputElement | null>(null);
// 5개의 미리보기 영역을 위한 배열
const previewPlaceholders = Array(5).fill(null);
return (
<div className="grid grid-cols-5 gap-4">
{previewPlaceholders.map((_, index) => (
<div key={index} className="aspect-square bg-gray-300 rounded-lg">
<span>이미지 추가</span>
</div>
))}
</div>
);
};
.
- fileInputRef: 파일 입력 요소에 접근할 수 있게 useRef를 사용하여 생성하고
사용자가 파일을 선택할 때 클릭 이벤트를 직접 제어할 수 있도록 해유 - 미리보기 자리 배열: Array(5).fill(null)로 다섯 개의 미리보기 영역을 담아줄겁니닷
2. 이미지 상태 관리
이미지 관리를 위해 두 가지 상태가 필요했어유
- 실제 파일 객체 배열 (업로드용)
- 미리보기 URL 배열 (화면 표시용)
또 코드에서는 handleImageChange 함수를 통해 사용자가 업로드한
이미지를 미리볼 수 있게 하고 파일을 배열로 관리했어유
const [previews, setPreviews] = useState<string[]>([]);
const [imageFiles, setImageFiles] = useState<File[]>([]);
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
// URL.createObjectURL을 사용해 미리보기 URL 생성
const previewUrl = URL.createObjectURL(file);
setPreviews(prev => [...prev, previewUrl]);
setImageFiles(prev => [...prev, file]);
// 같은 파일을 다시 선택할 수 있도록 초기화
e.target.value = '';
};
- 상태 변수 선언
- previews: 이미지 미리보기 URL을 저장하는 배열로, <img src={previewUrl} />처럼 렌더링에 사용할 수 있슴두
- imageFiles: 실제 업로드할 File 객체들을 저장하는 배열로 서버 전송 시 사용됩니두
- handleImageChange 함수
- 파일 선택: 사용자가 파일을 선택하면 e.target.files 배열에서 첫 번째 파일만 가져옵니데이
- 미리보기 URL 생성: URL.createObjectURL(file)로 선택된 파일의 미리보기 URL을 생성하여 previews 배열에 추가혀유
- 파일 저장: 선택된 파일은 imageFiles 배열에 추가되어 나중에 서버로 전송할 수 있게 준비하구유
- 파일 선택 초기화: e.target.value = ''로 입력 필드의 값을 초기화하여 같은 파일을 반복해서 선택할 수 있도록혀유
3. 이미지 유효성 검사
파일 형식과 크기를 체크하는 validation 함수를 만들었는데유
validateImage 함수는 이미지 파일을 업로드할 때 특정 조건을 검사하여 유효성을 확인하는 기능을 해서
이를 통해 허용되지 않은 형식이나 크기가 큰 이미지를 방지합니더
const validateImage = (file: File) => {
const validTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
const maxSize = 5 * 1024 * 1024; // 5MB
if (!validTypes.includes(file.type)) {
alert("JPG, PNG, GIF, WEBP 형식의 이미지만 업로드 가능합니다.");
return false;
}
if (file.size > maxSize) {
alert("이미지 크기는 5MB 이하여야 합니다.");
return false;
}
return true;
};
- 파일 형식 검사:
- validTypes 배열에 정의된 형식(image/jpeg, image/png, image/gif, image/webp)만 허용해유
- 파일 형식이 validTypes에 포함되지 않으면 경고 메시지를 보여주고 false를 반환해유
- 파일 크기 검사:
- maxSize는 5MB로 설정하고 파일 크기가 이 값을 초과할 경우 경고 메시지를 표시하고 false를 반환합니더!
- 유효한 파일 반환:
- 형식과 크기가 모두 조건에 부합하면 true를 반환하여 파일이 유효하다는 것을 알려줘유
4. Supabase Storage에 업로드
이미지 업로드는 Promise.all을 사용해 병렬로 처리했구여
여러 개의 이미지를 Supabase 스토리지에 업로드하고
각 이미지에 대한 공개 URL을 가져오는 과정을 수행해줘유
const imageUrls = await Promise.all(
params.images.map(async (file) => {
// 파일명에 userId와 타임스탬프를 포함시켜 유니크하게 관리
const fileName = `${params.userId}/${Date.now()}`;
const { data, error } = await supabase.storage
.from("challenges")
.upload(fileName, file);
if (error) throw error;
// 업로드된 파일의 public URL 가져오기
const { data: { publicUrl } } = supabase.storage
.from("challenges")
.getPublicUrl(fileName);
return publicUrl;
})
);
- 이미지 업로드 반복 처리:
- params.images 배열에 있는 모든 파일을 Promise.all과 map을 통해 병렬로 처리하여 업로드합니닷
쉽게 말해서 params.images에 있는 이미지 파일들을 하나씩 map을 사용해 처리할 때
Promise.all로 감싸서 여러 파일을 동시에 업로드할 수 있게 한 거예요 - 각 파일에 대해 유니크한 파일명을 생성하기 위해 fileName을 ${params.userId}/${Date.now()} 형식으로 설정하여, userId와 Date.now() 타임스탬프를 조합하여 중복을 방지하여유
- params.images 배열에 있는 모든 파일을 Promise.all과 map을 통해 병렬로 처리하여 업로드합니닷
- 파일 업로드:
- supabase.storage.from("challenges").upload(fileName, file);을 통해 Supabase의 challenges 버킷에 파일을 업로드하구요
- 업로드가 실패할 경우, error가 발생하여 해당 에러를 throw하고 작업이 중단됩니데이
- 공개 URL 생성:
- 업로드된 파일의 공개 URL을 가져오기 위해 supabase.storage.from("challenges").getPublicUrl(fileName);을 호출합니데이!!!!
- 이 URL은 클라이언트 측에서 이미지를 표시하거나 사용할 수 있는 실제 이미지 링크예유
- URL 반환:
- 각 파일에 대한 publicUrl을 배열로 반환하여 최종적으로 imageUrls에 모든 이미지의 URL이 저장돼유
5. 이미지 삭제 기능
const handleDeleteImage = (index: number) => {
// 메모리 누수 방지를 위해 URL 객체 해제
URL.revokeObjectURL(previews[index]);
// 해당 인덱스의 이미지 제거
setPreviews(prev => prev.filter((_, i) => i !== index));
setImageFiles(prev => prev.filter((_, i) => i !== index));
};
URL.createObjectURL(file)을 호출하면 해당 파일의 URL을 메모리에 생성하고, 브라우저에서 이 URL을 통해 파일을 임시로 사용할 수 있게 되는데유 하지만 이 URL을 계속 사용하지 않으면 메모리만 차지하게 되고 이것이 쌓이면 메모리 누수가 발생할 수 있다고 하네유
URL 해제
URL.revokeObjectURL(previews[index])를 호출하면 해당 URL을 메모리에서 해제하여
브라우저 메모리 사용량을 줄이고 불필요한 URL 데이터를 제거할 수 있기 때문에
이미지 미리보기에서 이미지를 삭제할 때마다 URL.revokeObjectURL을 호출하면
메모리를 효율적으로 관리할 수 있는 겁니다요!
이렇게 다중으로 이미지를 업로드하는 과정을 기록해 보았는데여
앞으로 프로젝트를 더 진행하면서 개선해야할 사항이 있어유
개선할 수 있는 사항들
- 드래그 앤 드롭 지원
- 이미지 압축 기능 추가
- 이미지 순서 변경 기능
- 업로드 진행률 표시
- 실패한 업로드에 대한 재시도 기능
마치며
다중 이미지 업로드는 생각보다 고려할 점이 많은 기능이어서 시간이 꽤 들었는데여
이 경험을 통해 더 나은 파일 업로드 UX에 대해 고민해볼 수 있었습니다.
다음에는 드래그 앤 드롭이나 이미지 압축 같은 추가 기능을 구현해보고 싶네요! 😊
가능하다면 개선 사항을 모두 구현하고 싶어유..
'FrontEnd > Next.js' 카테고리의 다른 글
친환경 스토어 지도 개발 로그 1편 ( 데이터 분석 및 삽입 ) (1) | 2024.11.09 |
---|---|
react-hook-form으로 폼 유효성 검사 구현 (0) | 2024.11.04 |
OOTW 프로젝트 - 글 작성 구현하기 ( 이미지 업로드 ) (2) | 2024.10.18 |
next.js supabase 이미지 업로드 (0) | 2024.10.15 |
Next.js 메타데이터, 동적 메타데이터, params (1) | 2024.10.08 |