Khủng Hoảng HEIC: Khi Apple Bắt Trình Duyệt Phải "Khóc" & Cách Giải Cứu Bằng Code
Câu chuyện muôn thuở: Một buổi chiều thứ Sáu, team CSKH báo lên: "Anh ơi, có khách hàng VIP dùng iPhone 15 Pro Max phàn nàn là tải ảnh chứng minh thư lên app toàn bị lỗi, màn hình đen thui không thấy hình!"
Bạn lật đật mở Database ra check. File vẫn lưu vào server thành công, đường dẫn URL trả về đúng chuẩn. Nhưng khi bạn copy cái link đó dán vào Google Chrome, thay vì hiển thị bức ảnh, Chrome lại... tải luôn cái file đó xuống máy. Nhìn kỹ lại cái đuôi file: avatar.heic.
Vâng, chào mừng bạn đến với cơn ác mộng mang tên HEIC (High-Efficiency Image Container).
1. HEIC là gì và tại sao nó lại là "Cục nợ" của Web Dev?
Từ iOS 11, Apple quyết định chia tay JPEG và chọn HEIC làm định dạng ảnh mặc định trên iPhone. Lý do của Tim Cook rất hợp lý: HEIC giữ được chất lượng ảnh cực cao nhưng dung lượng chỉ bằng một nửa so với JPEG.
Nhưng Apple quên (hoặc không thèm) nói với thế giới web một điều: Hầu hết các trình duyệt phổ biến (Chrome, Edge, Firefox) đều KHÔNG hỗ trợ hiển thị thẻ <img src="image.heic">. (Safari thì được, tất nhiên rồi).
Kết quả? Người dùng iPhone tung tăng tải ảnh lên. Server của bạn ngây thơ lưu lại. Và khi Frontend gọi xuống để hiển thị cho các user dùng Windows hay Android xem, mọi thứ vỡ vụn.
Một Vibe Coder không bao giờ bảo khách hàng: "Chị ơi chị vào Cài đặt iPhone đổi đuôi ảnh sang JPEG giúp em". Khách hàng là thượng đế. Chúng ta phải xử lý nó bằng Code!
2. Tư Duy Xử Lý: "Lò Luyện Đan" Đặt Ở Đâu?
Để biến HEIC thành JPEG/PNG, chúng ta có 2 ngã rẽ kiến trúc. Chọn đường nào phụ thuộc vào "Vibe" của team bạn:
- Cách 1: Xử lý tại Frontend (Client-side). Bắt trình duyệt của user tự chuyển đổi trước khi upload lên Server.
- Ưu điểm: Cực kỳ tiết kiệm CPU cho Server Backend. Server chỉ việc nhận file JPEG tiêu chuẩn.
- Nhược điểm: User dùng máy quá cũ có thể bị lag vài giây lúc convert.
- Cách 2: Xử lý tại Backend (Server-side). Frontend cứ ném file HEIC lên, Backend sẽ hứng và dùng CPU của server để convert.
- Ưu điểm: Trải nghiệm upload của user mượt mà, Frontend đỡ viết code.
- Nhược điểm: Tốn tài nguyên Server. Lỡ 1000 người cùng upload HEIC lúc Flash Sale, Server giật tung chảo.
Dưới đây, mình sẽ hướng dẫn cả 2 cách để anh em tùy cơ ứng biến.
3. Giải Pháp 1: Chặn Ngay Từ Cửa (Frontend x heic2any)
Đây là phương pháp mình khuyên dùng nhất. Hãy để máy của user tự làm việc của nó. Trên Frontend (React/Vue/Vanilla), thư viện thần thánh nhất để làm việc này là heic2any.
Cài đặt:
npm install heic2any
Code thực chiến (Bắt sự kiện lúc chọn file):
import heic2any from "heic2any";
async function handleFileUpload(event) {
let file = event.target.files[0];
// Kiểm tra nếu là file HEIC
if (file && (file.type === "image/heic" || file.name.toLowerCase().endsWith(".heic"))) {
console.log("Phát hiện file HEIC, đang tiến hành 'độ' lại...");
try {
// Chuyển HEIC sang Blob của JPEG
const convertedBlob = await heic2any({
blob: file,
toType: "image/jpeg",
quality: 0.8 // Giữ chất lượng 80% cho nhẹ
});
// Gói Blob thành File object để chuẩn bị gửi lên API
file = new File([convertedBlob], file.name.replace(/\.heic$/i, ".jpg"), {
type: "image/jpeg"
});
console.log("Convert thành công, file mới:", file.name);
} catch (error) {
console.error("Lỗi khi convert HEIC:", error);
alert("Ảnh của bạn quá phức tạp, vui lòng thử ảnh khác!");
return;
}
}
// Tới đây thì file chắc chắn là JPEG/PNG. Cứ fetch/axios gửi lên Backend như bình thường!
await uploadToServer(file);
}
Cực kỳ mượt mà! Backend của bạn thậm chí không biết sự tồn tại của file HEIC đó.
4. Giải Pháp 2: Lò Luyện Đan Trên Backend (Node.js)
Nếu bạn làm API Public và không thể ép client tự convert, Backend bắt buộc phải ra tay.
Trong Node.js, thư viện xử lý ảnh xịn nhất là sharp. Tuy nhiên, sharp đòi hỏi server phải cài thêm thư viện C++ (libvips có hỗ trợ HEIF), cực kỳ "hành xác" khi deploy qua Docker.
Vì vậy, giải pháp "chữa cháy" an toàn và dễ cài đặt nhất là dùng package heic-convert (Nó dùng WebAssembly, chạy được ở mọi môi trường mà không cần cài C++).
Cài đặt:
npm install heic-convert
Code thực chiến (Trong Route xử lý Upload):
const heicConvert = require('heic-convert');
const fs = require('fs');
async function processUploadedImage(req, res) {
let imageBuffer = req.file.buffer; // Buffer file lấy từ Multer
let fileName = req.file.originalname;
// Kiểm tra định dạng
if (fileName.toLowerCase().endsWith('.heic')) {
console.log('Backend nhận file HEIC, đang huy động CPU...');
try {
// Convert Buffer HEIC thành Buffer JPEG
imageBuffer = await heicConvert({
buffer: imageBuffer, // Đầu vào
format: 'JPEG', // Đầu ra
quality: 0.8 // Chất lượng
});
// Đổi đuôi file
fileName = fileName.replace(/\.heic$/i, '.jpg');
console.log('Convert thành JPEG trên server thành công!');
} catch (err) {
return res.status(500).json({ error: 'Không thể đọc được ảnh HEIC này' });
}
}
// Tiến hành lưu imageBuffer vào ổ cứng HDD hoặc đẩy lên AWS S3
fs.writeFileSync(`./uploads/${fileName}`, imageBuffer);
res.json({ message: 'Upload thành công', url: `/uploads/${fileName}` });
}
Lời kết
Việc phải chạy theo những "tiêu chuẩn riêng" của các ông lớn công nghệ (như Apple) là một phần của nghề Web Developer. Một Vibe Coder sẽ không ca thán, mà xem đó là cơ hội để làm cho trải nghiệm (UX) của ứng dụng trở nên hoàn hảo, bất chấp người dùng đang cầm thiết bị gì trên tay.
Lần tới thấy file .heic, hãy mỉm cười và ném nó vào lò luyện đan nhé!
Chủ đề tiếp theo: Đừng Bắt Backend Phải Gánh Tạ - Kiến Trúc Upload File Trực Tiếp Lên S3 Bằng Presigned URL
Trong đoạn code Backend ở trên, có một vấn đề trí mạng về thiết kế hệ thống. Dù bạn có convert file hay không, việc Frontend ném một cục file (Ảnh, Video) thẳng vào con server API của bạn là một hành động tự sát nếu hệ thống có traffic lớn. Cục file đó sẽ chiếm dụng băng thông mạng và RAM của Backend, khiến các request gọi logic API khác bị nghẽn (Block).
Làm sao để user upload cái Video 500MB mà con server Node.js của chúng ta không hề mất một giọt mồ hôi, thậm chí băng thông bằng 0?
Ở bài viết tới, mình sẽ bật mí một "Tuyệt kỹ kiến trúc đám mây" được sử dụng ở mọi công ty lớn: AWS S3 Presigned URL. Frontend sẽ được cấp một "thẻ bài" để đi cửa sau, ném file thẳng vào kho S3 mà không cần đi qua cửa chính Backend. Anh em nhớ follow để thay đổi hoàn toàn tư duy Upload nhé!
All rights reserved