Kiến trúc upload chuẩn
KHÔNG upload file qua server backend → server bị bottleneck. Thay vào đó: client upload trực tiếp lên S3 bằng pre-signed URL. Server chỉ tạo URL cho phép upload.
// File: presigned.ts
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const s3 = new S3Client({ region: "ap-southeast-1" })
// API: tạo pre-signed URL để client upload trực tiếp
async function getUploadUrl(req: Request, res: Response) {
const key = `uploads/${req.user.id}/${Date.now()}-${req.body.filename}`
const command = new PutObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: key,
ContentType: req.body.contentType,
})
const uploadUrl = await getSignedUrl(s3, command, {
expiresIn: 300, // URL hết hạn sau 5 phút
})
res.json({ uploadUrl, key })
}
// Client: upload trực tiếp lên S3 (không qua server)
// await fetch(uploadUrl, { method: "PUT", body: file })
Image optimization: không ai chờ ảnh 5MB
| Kỹ thuật | Giảm dung lượng | Cách dùng |
|---|---|---|
| WebP format | 25-35% | Convert bằng sharp library hoặc CDN auto |
| Resize theo viewport | 50-80% | Tạo nhiều size: thumbnail, medium, large |
| Lazy loading | Không load ảnh ngoài viewport | loading=“lazy” attribute |
| CDN (Cloudflare) | Cache gần user, load nhanh | Trỏ domain qua Cloudflare |
💡 Cloudflare R2 > S3
Cloudflare R2 = S3-compatible nhưng KHÔNG TỐN PHÍ bandwidth (egress). S3 tính tiền mỗi lần user xem ảnh. R2 miễn phí egress → tiết kiệm 80%+ cho app có nhiều hình.
❓ Vì sao dùng pre-signed URL thay vì upload qua backend server?
- Tạo pre-signed URL cho S3/R2
- Client upload trực tiếp bằng pre-signed URL
- Resize ảnh và convert WebP
- Setup CDN (Cloudflare) cho static assets