OPEN HYPER STEP
← 목록으로 (master-project)
MASTER-PROJECT · 19 / 50
master-project
CHAPTER 19 / 50
읽기 약 2
FUNCTION

파일 업로드 UI: 드래그앤드롭 + 미리보기


핵심 개념

Supabase Storage + 드래그앤드롭 + 진행률 + 미리보기 + 보안 — 이미지·문서 업로드 표준.

본문

Supabase Storage 버킷 설정

SQL📋 코드 (12줄)
-- supabase/migrations/20260429000003_storage.sql
insert into storage.buckets (id, name, public)
values ('uploads', 'uploads', false);

-- RLS: 본인 폴더만 접근
create policy "users upload own"
on storage.objects for insert
with check (bucket_id = 'uploads' and (storage.foldername(name))[1] = auth.uid()::text);

create policy "users read own"
on storage.objects for select
using (bucket_id = 'uploads' and (storage.foldername(name))[1] = auth.uid()::text);

드래그앤드롭 컴포넌트

TSX📋 코드 (76줄)
// src/components/upload-zone.tsx
'use client'
import { useState, useCallback } from 'react'
import { Upload, X } from 'lucide-react'
import { createClient } from '@/lib/supabase/client'

export function UploadZone({ onUpload }: { onUpload: (path: string) => void }) {
  const [dragOver, setDragOver] = useState(false)
  const [progress, setProgress] = useState<number | null>(null)
  const [preview, setPreview] = useState<string | null>(null)
  const supabase = createClient()

  const handleFile = useCallback(async (file: File) => {
    if (file.size > 10 * 1024 * 1024) return alert('10MB 이하만 가능')
    if (!['image/png', 'image/jpeg', 'image/webp'].includes(file.type)) return alert('이미지만 가능')

    setPreview(URL.createObjectURL(file))
    const { data: { user } } = await supabase.auth.getUser()
    if (!user) return

    const path = `${user.id}/${Date.now()}-${file.name}`
    setProgress(0)
    const { error } = await supabase.storage.from('uploads').upload(path, file, {
      cacheControl: '3600',
      upsert: false,
    })
    setProgress(100)
    if (error) return alert(error.message)
    onUpload(path)
  }, [supabase, onUpload])

  return (
    <div
      onDragOver={e => { e.preventDefault(); setDragOver(true) }}
      onDragLeave={() => setDragOver(false)}
      onDrop={e => {
        e.preventDefault()
        setDragOver(false)
        const file = e.dataTransfer.files[0]
        if (file) handleFile(file)
      }}
      className={`border-2 border-dashed rounded-lg p-8 text-center transition-colors ${
        dragOver ? 'border-primary bg-primary/5' : 'border-border'
      }`}
    >
      {preview ? (
        <div className="relative inline-block">
          <img src={preview} alt="" className="max-h-40 rounded" />
          <button onClick={() => setPreview(null)} className="absolute -top-2 -right-2 bg-red-500 rounded-full p-1">
            <X className="h-3 w-3 text-white" />
          </button>
        </div>
      ) : (
        <>
          <Upload className="h-8 w-8 mx-auto mb-2 text-muted-foreground" />
          <p className="text-sm">파일을 드래그하거나 클릭하세요</p>
          <input
            type="file"
            accept="image/*"
            onChange={e => e.target.files?.[0] && handleFile(e.target.files[0])}
            className="hidden"
            id="upload-input"
          />
          <label htmlFor="upload-input" className="text-primary text-sm cursor-pointer underline mt-2 inline-block">
            파일 선택
          </label>
        </>
      )}
      {progress !== null && (
        <div className="mt-3 h-1 bg-muted rounded">
          <div className="h-full bg-primary rounded transition-all" style={{ width: `${progress}%` }} />
        </div>
      )}
    </div>
  )
}

Signed URL (Private 파일)

TS📋 코드 (5줄)
// 1시간 유효 URL
const { data } = await supabase.storage
  .from('uploads')
  .createSignedUrl(path, 3600)
// data.signedUrl → <img src={...} />

보안 체크리스트

📋 코드 (8줄)
✓ 파일 크기 제한 (10MB 이하)
✓ MIME 타입 화이트리스트
✓ 확장자 검증 (.exe 차단)
✓ 사용자별 폴더 (RLS)
✓ Public bucket 사용 금지 (필요한 것만)
✓ Signed URL TTL 짧게 (1시간)
✓ 바이러스 스캔 (큰 서비스 ClamAV)
✓ Rate limit (1분당 10개)

이미지 최적화 (Next.js Image)

TSX📋 코드 (10줄)
import Image from 'next/image'

<Image
  src={publicUrl}
  alt=""
  width={400}
  height={300}
  className="rounded"
  loading="lazy"
/>

다음 챕터

CH.20 "실시간 알림: 토스트 + 벨 아이콘".


AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude

무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6

내 마스터 프로젝트의 파일 업로드 부분을 분석해서
실전 적용 + 개선 우선순위 3가지를 알려줘.
ChatGPT

무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro

파일 업로드 관련 모범 사례·안티패턴 5개를
비교 분석해서 실전 적용를 위한 추천 방안을 알려줘.
Gemini

무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro

내 프로젝트 전체에서 파일 업로드
최적화 가능 위치와 리스크를 보고해줘.
Grok

무료: Grok 4.1 / SuperGrok $30/mo

2026년 한국 1인 개발자 시장의
파일 업로드 트렌드와 차별화 포인트를 정리해줘.

⭐ 이것만 기억하세요
파일 업로드 UI: 드래그앤드롭 + 미리보기 이 3가지만 확실히 잡으세요
1.Supabase Storage = S3 호환 + RLS 통합
2.드래그앤드롭 + 진행률 + 미리보기 = UX 핵심
3.다음 챕터에서 실시간 알림


공유하기
진행도 19 / 50