[Svelte 시리즈 14편] 파일 업로드 UI와 진행 상태

English version

과제 제출이나 프로젝트 스크린샷 업로드는 기다림이 생기는 작업입니다. 진행률과 재시도 방식을 명확히 보여 주어야 학생들이 불안해하지 않습니다.

이번 글에서 새로 나오는 용어

  1. 멀티파트 전송: 파일을 여러 조각으로 나눠 보내는 HTTP 방식으로, 폼에 enctype="multipart/form-data"를 지정해야 합니다.
  2. 진행률 이벤트: 업로드 중 onprogress로 전달되는 정보로, 전송된 바이트를 기준으로 퍼센트를 계산합니다.
  3. XMLHttpRequest: 세밀한 업로드 제어와 진행률 이벤트를 제공하는 오래된 브라우저 API입니다.
  4. AbortController/xhr.abort(): 사용자가 중단을 누르면 네트워크 요청을 즉시 끊어 주는 취소 도구입니다.

개념

멀티파트 전송(multipart transfer, 다중 부분 전송)은 파일을 담아 보내는 HTTP 방식입니다. 폼에 enctype="multipart/form-data"를 설정해야 브라우저가 파일을 올바로 전송합니다. 진행률(progress, 완료 비율)은 업로드 중 전송된 바이트 비율로 계산합니다. XMLHttpRequestfetch + ReadableStream으로 이벤트를 받을 수 있습니다. 취소는 xhr.abort()AbortController(요청 취소 컨트롤러)로 구현해야 사용자 선택을 바로 반영할 수 있습니다.

코드

폼, 진행률 처리, 서버 액션을 순서대로 정리합니다.

<script>
  let pending = false;
  let progress = 0;

  function handleUpload({ action, form }) {
    const xhr = new XMLHttpRequest();
    xhr.upload.onprogress = (event) => {
      if (!event.lengthComputable) return;
      progress = Math.round((event.loaded / event.total) * 100);
    };
    xhr.onloadstart = () => { pending = true; };
    xhr.onloadend = () => { pending = false; progress = 0; };
    xhr.open('POST', action);
    xhr.send(new FormData(form));
    return () => xhr.abort();
  }
</script>

<form method="POST" enctype="multipart/form-data" use:enhance={handleUpload}>
  <input type="file" name="attachments" accept="image/*,application/pdf" multiple required />
  <textarea name="comment" placeholder="메모" rows="3"></textarea>
  <button type="submit" disabled={pending}>{pending ? '업로드 중' : '제출'}</button>
</form>

<div class="progress" aria-live="polite">
  <div class="bar" style={`width:${progress}%`}>
    <span>{progress}%</span>
  </div>
</div>
// src/routes/(app)/uploads/+page.server.ts

export const actions = {
  default: async ({ request, locals }) => {
    const data = await request.formData();
    const files = data.getAll('attachments');
    if (files.length > 5) {
      return fail(400, { error: '파일은 최대 5개까지 업로드 가능합니다.' });
    }
    await saveFiles(locals.user.id, files);
    return { success: true };
  }
};

handleUpload에서 반환한 함수가 컴포넌트가 파괴될 때 실행되어 업로드를 취소합니다. 서버에서는 파일 수와 용량을 다시 확인해 악의적인 요청을 막아야 합니다.

왜 중요한가

대용량 파일 업로드는 실패 가능성이 높습니다. 진행률과 취소 버튼이 없으면 사용자가 브라우저를 닫아 버릴 수 있습니다. 명확한 상태 표시와 재시도 경로를 제공하면 교내 과제 제출 페이지나 콘테스트 자료 업로드에서도 불필요한 문의를 줄일 수 있습니다.

실습

  • 따라 하기: use:enhance={handleUpload} 패턴으로 멀티 파일 폼을 만들고 진행률 바를 표시한다.
  • 확장하기: 취소 버튼과 재시도 버튼을 추가해 xhr.abort() 후 상태를 초기화한다.
  • 디버깅: 진행률이 0%에서 멈추면 event.lengthComputableenctype 설정을 확인한다.
  • 완료 기준: 5개 이하 파일 업로드가 성공하고, 실패 후 다시 시도할 수 있다.

마무리

업로드 흐름을 정리하면 사용자가 기다리는 동안에도 안심할 수 있습니다. 다음 편에서는 컴포넌트 접근성과 반응형 내비게이션을 점검해 전체 경험을 다듬습니다.

💬 댓글

이 글에 대한 의견을 남겨주세요