Luồng tôi thiết kế:
1. Chọn file + validate sớm. <input type="file"> hoặc drag-drop; kiểm tra type/size ngay phía client (chặn file quá cỡ trước khi tốn băng thông). Server vẫn phải validate lại — client chỉ là UX.
2. Preview. URL.createObjectURL(file) tạo URL tạm gán vào <img>/<video> — nhanh và không đọc cả file vào bộ nhớ như FileReader. Nhớ gọi URL.revokeObjectURL khi xong để tránh leak.
3. Upload + progress. Gửi bằng FormData (đừng tự set Content-Type — trình duyệt tự thêm boundary). fetch không có upload progress → dùng XMLHttpRequest với xhr.upload.onprogress (hoặc axios onUploadProgress):
const xhr = new XMLHttpRequest()
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) setProgress(e.loaded / e.total)
}
xhr.open('POST', '/upload')
xhr.send(formData)4. Hủy. xhr.abort() (hoặc AbortController nếu upload qua fetch/axios); UI phải có nút hủy khi đang tải và dọn state sau khi hủy.
5. File lớn → chunk upload. Cắt file bằng file.slice(start, end) thành từng phần (vd 5MB), upload kèm index để server ghép lại. Lợi ích: retry từng chunk khi rớt mạng thay vì tải lại từ đầu, và làm được resumable (hỏi server đã nhận tới chunk nào rồi tiếp tục).
Ghi chú thực tế: với object storage (S3, GCS), FE thường xin presigned URL từ backend rồi upload thẳng lên storage — file không đi qua server ứng dụng.