게시글 이미지 업로드 (Delayed upload)

alirz-pixel·2025년 11월 14일

backend

목록 보기
3/7

이미지 업로드 방식

찾아보고 고민해본 결과 총 3가지 방식으로 이미지 업로드 기능을 구현가능해 보인다.
1. Delayed image upload
2. 임시 이미지 업로드
3. 이미지 업로드 후 스케줄링 기반 정리

해당 글에서는 delayed image upload에 대해서 작성한다.
구현 방식에는 정답이 없으므로 장단점을 고려해 선택하는 것을 추천합니다.

Delayed image upload

흐름

  1. 사용자가 이미지를 입력
  2. 웹에서는 해당 이미지를 BASE64로 보여줌 (서버로 저장 X)
  3. 게시글 저장 시,
    3-1. 서버에 이미지들을 업로드한 뒤, URL을 반환 받음
    3-2. BASE64 -> URL 치환 -> 게시글 HTML 저장

장점

  1. 서버 트래픽 최소화: 게시글 업로드 전까지 트래픽을 사용하지 않음
  2. 저장 공간 최소화: 게시글에 저장될 이미지만 서버에 저장됨 (임시 이미지가 저장되지 않음)

단점

  1. 브라우저 메모리 부담 증가: BASE64는 원본보다 용량이 약 33% 증가
  2. 업로드 시 트래픽 집중: 이미지가 많은 게시글에서는 서버로 한 번에 트래픽이 몰릴 수 있음

구현

1) 이미지 미리보기 및 pendingImage

해당 코드에서는 서버로 업로드 될 이미지를 담는 pendingImagesBASE64로 된 이미지 출력을 메인으로 보면 된다.
또한, 이후 BASE64에서 치환할 file을 트래킹하기 위해 data-index에는 pendingImages의 인덱스도 같이 저장한다.

const pendingImages = []; // 서버 업로드 예정 이미지 저장
const previewContainer = document.getElementById('preview');
const input = document.getElementById('imageInput');

input.addEventListener('change', (e) => {
    const files = Array.from(e.target.files);

    files.forEach((file) => {
        // 1) pendingImages 배열에 저장
        const index = pendingImages.length;
        pendingImages.push(file);

        // 2) FileReader로 Base64 변환
        const reader = new FileReader();
        reader.onload = function(event) {
            // 3) 이미지 요소 생성 및 미리보기
            const img = document.createElement('img');
            img.src = event.target.result; // Base64
            // BASE64에서 치환할 file을 트래킹하기 위한 index 저장
            img.setAttribute('data-index', index);

            previewContainer.appendChild(img);
        };
        reader.readAsDataURL(file);
    });
});

2) pendingImages에 저장된 이미지 서버에 저장

해당 코드에서는 pendingImages에 쌓인 이미지들을 서버에 저장하고,
반환받은 URL로 HTML에 치환하여 게시글을 저장하는 방식이다.
(간결한 코드를 위해 따로 예외처리는 되어있지 않습니다.)

// 게시글 제출 시 서버 업로드 + BASE64 → URL 치환
submitBtn.addEventListener('click', async () => {
    // 1) FormData로 서버 업로드
    const formData = new FormData();
    pendingImages.forEach(file => formData.append('files', file));

    // 서버에 이미지 업로드 (예: /api/upload-images)
    const res = await fetch('/api/upload-images', {
        method: 'POST',
        body: formData
    });
    const uploadedUrls = await res.json(); // ["image_url_1.png", "image_url_2.png", ...]

    // 2) Base64 → 서버 URL 치환
    const imgs = previewContainer.querySelectorAll('img');
    imgs.forEach(img => {
        const index = parseInt(img.getAttribute('data-index'));
        img.src = uploadedUrls[index];
    });

    // 3) 게시글 내용 생성 (HTML 포함)
    const title = document.getElementById('title').value;
    const contentHTML = previewContainer.innerHTML;

    // 4) 게시글 서버 전송
    await fetch('/api/post', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            title,
            content: contentHTML
        })
    });

    alert('게시글 작성 완료!');
});

0개의 댓글