TL;DR
- 프론트는 여러장 업로드가 가능했지만, 백엔드에서는 단일 파일만 처리해서 마지막에 업로드 되는 이미지 한 장만 처리되고 있었다
- 해결은 "이미지를 배열로 받기 -> 파일명 리스트를 JSON으로 저장 -> 조회 시 리스트로 복원 후 슬라이드로 랜더링" 구조로 FE-BE 전체 흐름을 통일하는 방식으로 이룸
문제 상황
UI와 DB의 missmatch
상품 등록 화면에서 사진을 여러장 선택하여 올리고, 상품 상세보기 페이지에서는 그 사진들을 슬라이드 형태로 넘겨볼 수 있는 형태를 구현하려고 하였습니다. 프론트엔드에서는 multiple이 있는 <input type="file">을 사용하였고, 여러장을 선택하면 미리보기 썸네일까지 잘 뜨는 것 처럼 보였습니다. 하지만 실제로 상품을 등록하고 DB에 저장된 데이터를 확인해본 결과, 여러장을 올려도 마지막에 선택한 사진 한 장만 저장되고, 상품/리뷰 상세 페이지에는 슬라이드가 아닌 단일 이미지만 표시되는 치명적인 문제가 발생하였습니다. 즉, UI는 다중 이미지 업로드를 지원하였으나, 백엔드의 데이터 처리구조는 단일 파일에 머물러있는 불완전한 상태였습니다. 다중 이미지 업로드 기능은 상품 정보와 리뷰의 핵심 UX이기에 반드시 해결해야 했습니다
원인 분석
파이썬 프로젝트를 실행시켜 로컬 서버를 켜 둔 후, 상품을 등록해보며 DB에 저장된 이미지 경로 값을 확인해보며 원인 추적을 해보았습니다. 그 과정에서 아래 두 가지 문제를 발견했습니다
[1] 백엔드가 파일을 배열이 아닌 단일 값으로만 처리
ProductForm 에서는 파일 필드를 단일 필드로 정의해 두었고, 서버에서는 form.file.data 기준으로만 처리하였어서 여러장이 들어와도 마지막 파일만 계속 덮어쓰는 구조로 설계되었었습니다.
[2] DB에 저장된 이미지 정보가 리스트가 아닌 하나의 문자열 덩어리
여러장을 JSON 문자열로 저장하거나, 배열로 관리하는 로직이 없어 템플릿에서 반복문을 적어놓아도 계속 한장만 보이는 구조였습니다. 즉, 프론트에서는 multiple으로 정의하고, 백엔드에서는 single이라고 정의하여 생긴 불일치 때문에 이미지 다중 업로드가 안되고 마지막 이미지만 보이는 현상이 생긴 것 이었습니다.
해결 과정
[1] 업로드 단계
여러장을 배열로 받아 JSON으로 저장하기
먼저 상품 등록 라우트에서 여러 파일을 한번에 받는 구조로 코드를 수정했습니다. request.files.getlist("files")를 사용하여 files 배열로 받고, 각 파일을 static/images에 저장한 뒤, 파일명 리스트를 json.dumps로 직렬화해 img_path에 저장하였습니다.
핵심 아이디어: DB에는 하나의 문자열로 저장하되, 그 문자열 속에는 파일명 리스트(JSON)을 넣고, 나중에 꺼낼때 다시 리스트로 복원해서 쓴다. 이렇게 하여 상품과 리뷰 양쪽에서 동일한 패턴으로 이미지 배열을 다룰 수 있었습니다.
프론트엔드(reg_product.html, reg_review.html)쪽에서는, 사용자가 파일을 여러번에 나누어 선택해도 최대 5장까지 누적 되도록 Data Transfer 객체를 활용해 input.files를 직접 덮어쓰는 방식으로 구현했습니다. 이 덕분에 브라우저에서는 여러 차례선택 후 한번에 업로드가 가능해졌습니다. 또 등록 화면에서는 첫번째 이미지를 메인 미리보기 영역에 표시하고, 나머지 이미지는 작은 미리보기 형태로 제공하여 사용자가 업로드한 사진들의 구성을 직관적으로 확인할 수 있도록 하였습니다. 이러한 구조는 상세 페이지에서도 그대로 이어져, 업로드된 이미지 배열이 슬라이드 UI로 자연스럽게 연결되며, 사용자는 여러 장의 상품 이미지를 좌-우 이동 버튼을 통해 편리하게 탐색할 수 있게 만들었습니다.
[2] 상세 페이지
JSON->리스트로 복원 + 슬라이드 구성 업로드가 끝나면 상품 상세 페이지(/products/<id>/<slug>), 리뷰 상세 페이지에서 이미지를 다시 읽어 슬라이드로 보여주기 기능도 있어야 했습니다. products.py의 view_product 라우트 안에서 다음과 같은 처리를 추가했습니다.
- DB에서 가져온 img_path(JSON 문자열)을 json.loads로 파싱하여 images 리스트를 생성
- 예전 데이터처럼 단일 문자열들만 들어 있는 경우에도 안전하게 ["one.jpg"] 형태로 보정
- 최종 리스트를 product["images"] 에 넣어 템플릿으로 넘기기
- 템플릿인 product_detail.html에서는 product.images가 있다면 그걸 쓰고, 없다면 product.img_path 하나를 리스트로 감싸서 image_list를 만듦
- image_list를 for루프로 순회하면서 .gallery-slide 안에 <img>를 여러장 렌더링 하고, 이 슬라이드들을 좌우 버튼과 translateX 애니메이션으로 넘길 수 있게 했습니다.
이 구조를 상세 리뷰 페이지에도 적용하여 동일한 UX를 공유하도록 하였습니다.
[3] 목록 페이지
첫번째 이미지를 썸네일로 사용하기
마지막으로, 상품 전체 조회나 메인 인덱스 페이지 등 상품이나 리뷰를 카드로 보여주는 화면에서는 모든 이미지를 다 보여줄 필요는 없고, 대표 썸네일 한 장만 있으면 됩니다. 그래서 view_products()안에서 페이지별 item 목록을 만들때 각 상품의 img_path JSON 문자열을 다시 리스트로 복원한 뒤, images[0]만 꺼내 대표 이미지로 사용하도록 가공했습니다.
이렇게 하여
상세 페이지 : 여러장 슬라이드
목록&메인 카드 : 첫번째 이미지
형식으로 역할이 분담되었습니다
결과


팁... 그리고 느낀점
이미지 다중 업로드를 지원하려면 처음 설계 단계부터 하나의 값이 아니라 리스트(배열)을 기준으로 데이터 흐름을 설계해야 합니다.
업로드(프론트) -> 요청 (백엔드) -> DB 저장 -> 조회 -> 템플릿 렌더링
이 모든 단계가 리스트를 사용한다는 전제로 맞물려야 마지막 한장만 저장되는 문제를 피할 수 있습니다. 또한 DB에서는 문자열 한 칸만 있어도, 그 안에 JSON 배열을 넣어두면 이후 상품/리뷰/메인 페이지 등 여러 화면에서 같은 패턴을 재사용 할 수 있게되어 유지가 편리해집니다.
특히 이 기술은 여러 페이지와 분야에서 활용되기 때문에 다중 이미지 업로드와 썸네일 이미지로 가공하는 등 이미지와 관련된 흐름 전체를 하나의 모듈처럼 설계했다는 점에서 의미가 컸던 디버깅 경험이었습니다.