Upload file trong Next.js App Router hơi khác một chút so với Pages Router vì chúng ta dùng Server Actions.
1. Client-side Preview (UX là vua)
Đừng bắt user chờ upload xong mới thấy ảnh. Hãy hiện ảnh ngay khi họ chọn file.
tsx:
function AvatarUpload() {
const [preview, setPreview] = useState<string | null>(null);
const { register } = useForm();
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
// 🚀 Tạo URL tạm thời để hiện ảnh ngay lập tức
const objectUrl = URL.createObjectURL(file);
setPreview(objectUrl);
// Cleanup để tránh leak memory
return () => URL.revokeObjectURL(objectUrl);
}
};
return (
<div>
{preview && <img src={preview} className="w-20 h-20 rounded-full" />}
<input
type="file"
accept="image/*"
{...register("avatar")}
onChange={handleFileChange}
/>
</div>
);
}2. Validate File với Zod
Zod có thẻ validate file instance cực mạnh.
ts:
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const ACCEPTED_IMAGE_TYPES = ["image/jpeg", "image/jpg", "image/png", "image/webp"];
const UploadSchema = z.object({
avatar: z
.any()
.refine((files) => files?.length == 1, "Image is required.")
.refine((files) => files?.[0]?.size <= MAX_FILE_SIZE, `Max file size is 5MB.`)
.refine(
(files) => ACCEPTED_IMAGE_TYPES.includes(files?.[0]?.type),
"Only .jpg, .jpeg, .png and .webp formats are supported."
),
});3. Server Action Upload (S3/R2)
Lưu ý: Bạn KHÔNG THỂ lưu file vào folder public khi deploy lên Vercel (Serverless Read-only). Bạn phải dùng dịch vụ lưu trữ ngoài (AWS S3, Cloudflare R2, Uploadthing).
tsx:
// actions.ts
'use server';
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
const s3 = new S3Client({ ... });
export async function uploadAvatar(formData: FormData) {
const file = formData.get("avatar") as File;
// Convert File -> Buffer
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
// Upload lên S3
await s3.send(new PutObjectCommand({
Bucket: "my-bucket",
Key: `avatars/${Date.now()}-${file.name}`,
Body: buffer,
ContentType: file.type,
}));
// Lưu URL vào DB...
}Kết luận
- Dùng
URL.createObjectURLđể preview ngay lập tức. - Dùng Zod để chặn file quá to ngay từ Client.
- Server Action xử lý File -> Buffer -> S3.