
안녕하세요. 리트머스입니다.
이번 포스팅에서는 여러개의 이미지를 압축하여 다운로드하는 기능에 대해서 알아보려고 합니다.
웹에서 이미지 파일은 가장 흔히 사용되고 다운로드되는 파일 형식 중 하나로, 이를 제대로 처리하는것은 사용자 경험을 향상시킨다는 점에서 중요합니다. 그러나 여러 이미지를 한 번에 다운로드하거나 압축 파일로 제공하려고 할 때, 예상치 못한 기술적 제약이 발생할 수 있습니다.
이 글에서는 Bubble.io에서 여러 이미지를 zip 파일로 다운로드하는 기능을 구현하는 과정을 자세히 다룹니다. 특히, 이미지 다운로드 과정에서 흔히 발생하는 CORS(Cross-Origin Resource Sharing) 오류와 그 원인을 파악하고, 이를 해결하기 위한 구체적인 방법을 설명합니다. 또한, Base64 변환 및 JSZip을 사용하여 이미지 데이터를 효율적으로 압축하고, 최종적으로 사용자가 간편하게 다운로드할 수 있도록 처리하는 방안을 제시합니다.
글을 다 읽고 나면, 다음과 같은 내용을 알 수 있습니다:
- 여러 이미지를 zip 파일로 다운로드하는 기능의 구체적인 구현 방법.
- 압축 다운로드 플러그인을 써도 다운로드가 잘 안 되는 이유
- 버블의 보안 방식과 브라우저 정책 이해
CORS 오류로 인한 이미지 다운로드 문제와 해결 방법
Bubble.io에서 이미지를 처리하는 과정 중, 여러 이미지를 압축하여 다운로드하려고 할 때 CORS(Cross-Origin Resource Sharing) 오류가 발생하는 경우가 있습니다. 이 문제는 Bubble에서 제공하는 이미지 URL 구조와 브라우저 정책에서 기인합니다. 본 글에서는 이러한 오류의 원인과 이를 해결하는 방법을 설명하고자 합니다.
1. 문제 원인: Bubble 이미지 URL의 구조
Bubble.io의 이미지 URL은 다음과 같이 구성됩니다:
https://xxxxxxxx.cdn.bubble.io/xxxxxxxx/image_name.jpg
처음 URL만 보면 일반적인 이미지 파일처럼 보이지만, 뒤에 붙는 파라미터가 문제를 야기합니다:
?_gl=1icn2dh_gcl_au*MTY0NjQzMTkzNi4xNzM0NjAzNzI4…
이 파라미터는 Bubble이 이미지 요청 시 토큰을 포함하여 이미지를 보호하기 위해 사용하는 메커니즘입니다. 따라서 외부 애플리케이션이나 API를 통해 이미지를 직접 요청하면 브라우저의 CORS 정책에 의해 차단됩니다.
CORS란? CORS는 웹 브라우저가 도메인 간 리소스 요청을 제어하는 보안 기능입니다. Bubble 이미지 URL에 포함된 토큰이 올바르지 않으면 요청이 차단됩니다.
2. 직접 접근
Bubble에서 제공하는 이미지 URL을 그대로 주소창에 입력하면 이미지를 정상적으로 열 수 있습니다. 이는 브라우저가 Bubble 서버에서 직접 이미지를 로드하기 때문입니다. 이를 풀어서 설명하면 다음과 같습니다.
- Bubble에서 제공하는 URL은 Bubble 서버의 CDN(Content Delivery Network)에서 이미지를 제공하는 주소입니다.
- 사용자가 Bubble 이미지 URL을 주소창에 입력하면, 브라우저는 Bubble 서버에 직접 요청을 보냅니다.
- 이 요청은 사용자의 브라우저에서 Bubble 서버로 보내지며, 동일한 도메인 간의 요청으로 간주됩니다.
- CORS 정책은 도메인 간 요청(Cross-Origin Request)을 제어하는 정책이므로, 동일 도메인 내에서의 요청은 차단되지 않습니다.
그러나 다른 도메인, 예를 들어 https://litmers.com과 같은 클라이언트 사이드 애플리케이션에서 API를 통해 이미지를 요청하면 CORS 오류가 발생합니다. 요청 도메인(https://litmers.com)과 이미지의 도메인(https://xxxxxxxx.cdn.bubble.io)이 다르기 때문입니다.
3. 해결 방법: 서버 사이드에서 이미지 처리
CORS 정책 문제로 인해 클라이언트 사이드에서는 Bubble.io의 이미지를 직접 다운로드하거나 변환하는 것이 불가능합니다. 따라서 다음과 같은 방식으로 서버 사이드에서 문제를 해결해야 합니다:
- Bubble 서버의 이미지 데이터를 Base64로 변환
- 서버 사이드 코드에서 Bubble 이미지 URL에 접근하여 이미지를 요청합니다.
- 응답 받은 이미지를 Base64 형식으로 변환합니다.
- 여러 이미지를 압축 파일로 묶기
- 변환된 Base64 데이터를 압축 도구를 사용하여 ZIP 파일로 묶습니다.
- 사용자에게 다운로드 제공
- 최종적으로 생성된 ZIP 파일을 클라이언트가 다운로드할 수 있도록 API로 제공합니다.
4. 구현 예시
다음은 이미지 url을 서버 사이드에서 base64로 변환해서 다운로드 하는 구현 예시입니다.
1. 이미지 url을 서버 사이드에서 base64로 변환



- 서버 사이드 (백앤드 워크플로우)에서 base64로 인코딩 하는 방법
- 백앤드 워크플로우에서 인코딩한 결과값을 클라이언트 사이드(일반 워크플로우)에서 받아오는 방법 - API Connector를 통해 콜합니다.
↗︎ endcoded in base64 에 대한 설명 바로가기
2. 압축 후 다운로드
Base64 이미지 데이터를 JavaScript를 사용해 ZIP 파일로 압축하고 다운로드하는 스크립트입니다.

// Base64 이미지 데이터 리스트
const base64Images = ["XXXXX.....];
// JSZip 인스턴스 생성
const zip = new JSZip();
// Base64 이미지 데이터를 zip 파일에 추가 base64Images.forEach((base64, index) => {
const fileName = `image_${index + 1}.png`; // 파일 이름 생성
zip.file(fileName, base64.split(",")[1], { base64: true }); // Base64 데이터에서 prefix 제거 후 추가
});
// 압축 파일 생성 및 다운로드
zip.generateAsync({ type: "blob" }).then(function (content) {
const a = document.createElement("a");
a.href = URL.createObjectURL(content);
a.download = "images.zip"; // 압축 파일 이름 a.click();
});
위 스크립트에 대한 설명은 다음과 같습니다.
전체 코드의 흐름
- Base64 데이터를 배열에 저장합니다.
- JSZip 객체를 생성해 ZIP 파일 작업을 시작합니다.
- 각 Base64 데이터를 순회하며 ZIP 파일에 추가합니다.
- ZIP 파일을 Blob으로 생성하고 임시 URL을 통해 다운로드 링크를 만들어 사용자가 ZIP 파일을 다운로드할 수 있게 합니다.
1. Base64 이미지 데이터 리스트 정의
const base64Images = ["long_base64_information_string_0","long_base64_information_string_1" ... ];
- 목적: Base64 형식으로 인코딩된 이미지 데이터를 배열 형태로 저장합니다.
- 설명: Base64 데이터는 이미지 파일을 텍스트 형식으로 표현한 데이터입니다. 여기서
base64Images는 여러 이미지를 저장하는 배열입니다. 각각의 요소는 이미지 하나에 해당합니다.
2. JSZip 인스턴스 생성
const zip = new JSZip();
- 목적: JSZip 라이브러리를 이용해 ZIP 파일을 생성하기 위한 인스턴스를 만듭니다.
- 설명: JSZip은 클라이언트 사이드에서 ZIP 파일을 생성할 수 있게 해주는 JavaScript 라이브러리입니다.
new JSZip()을 호출하면 ZIP 파일 생성 작업을 관리할 수 있는 객체가 생성됩니다.
3. Base64 데이터를 ZIP 파일에 추가
base64Images.forEach((base64, index) => {
const fileName = `image_${index + 1}.png`; // 파일 이름 설정
zip.file(fileName, base64, { base64: true }); // Base64 데이터 추가
});
- 목적: Base64 데이터를 순회하며 ZIP 파일에 추가합니다.
- 세부 설명:
base64Images.forEach: 배열의 각 요소(Base64 데이터)를 순회합니다.index: 현재 순회 중인 요소의 인덱스입니다.const fileName =image_${index + 1}.png;: ZIP 파일 내에서 사용할 이미지 파일의 이름을 동적으로 생성합니다. 예:image_1.png,image_2.png.zip.file(fileName, base64, { base64: true });: JSZip 객체에 파일을 추가합니다.fileName: 파일 이름.base64: 파일 데이터(Base64 형식).{ base64: true }: 데이터가 Base64 형식임을 명시합니다.
4. 압축 파일 생성 및 다운로드
zip.generateAsync({ type: "blob" }).then(function (content) {
const a = document.createElement("a");
a.href = URL.createObjectURL(content);
a.download = "images.zip"; // 압축 파일 이름
a.click();
});
- 목적: ZIP 파일을 생성하고 브라우저를 통해 다운로드합니다.
- 세부 설명:
zip.generateAsync({ type: "blob" }):- ZIP 파일을 비동기로 생성합니다.
type: "blob": 생성된 ZIP 파일 데이터를 Blob(Binary Large Object) 형식으로 반환합니다.
.then(function (content) { ... }):- ZIP 파일 생성 작업이 완료되면, 반환된 Blob 데이터를 처리합니다.
document.createElement("a"):- 다운로드 링크를 생성합니다.
<a>태그는 파일 다운로드를 트리거하는 데 자주 사용됩니다.
- 다운로드 링크를 생성합니다.
a.href = URL.createObjectURL(content);:- Blob 데이터를 임시 URL로 변환해 링크에 연결합니다.
a.download = "images.zip";:- 다운로드되는 ZIP 파일의 이름을 지정합니다.
a.click();:- 생성한 링크를 클릭해 다운로드를 시작합니다.
참고 라이브러리
↗︎ JSZip 공식 문서
참고 버블 문서
↗︎ Bubble Docs
결론
Bubble.io의 이미지 URL과 CORS 정책 때문에 클라이언트 사이드에서 직접 이미지를 처리하는 것은 불가능합니다. 그러나 서버 사이드에서 이미지 데이터를 Base64로 변환하고 이를 압축하여 제공하면 이러한 문제를 해결할 수 있습니다. 위 과정을 통해 사용자는 여러 이미지를 손쉽게 다운로드할 수 있습니다.
Bubble.io와 같은 플랫폼의 보안 메커니즘을 이해하고, 이에 맞는 서버 사이드 처리 방식을 구현하는 것은 사용자 경험을 향상시키는 데 중요한 요소입니다.








![[2025 최신] 버블에서 리피팅 그룹 각 셀마다 워크플로우를 실행하고 싶을 때 : 플러그인 오케스트라(Orchestra)](/_next/image?url=https%3A%2F%2Fuosmtaxndlzgvsnhbugi.supabase.co%2Fstorage%2Fv1%2Fobject%2Fpublic%2Fmedia%2F12-1.jpg&w=3840&q=75)