When iPhone Users Upload HEIC, Your Frontend Crashes: The Fix That Works
iPhone users uploaded a HEIC photo, and my frontend broke
Last year I built a mini-program with a feature that lets users upload ID photos for resumes. Testing went smoothly, but on the first day it went live we got feedback: "After uploading the photo, the page went white."
At first I assumed the image was too large, so I asked the user to try another one, but it still white-screened. Then I had the user screenshot from their photo album and send it over, and I saw the format was .HEIC.
Well, this is the pit I was bound to step in eventually.
What is HEIC?
HEIC (High Efficiency Image Container) is the default image format Apple has used since iOS 11, and its underlying encoding is HEVC (H.265). Compared with JPEG, at the same quality the file size is about half. So Apple has a good reason to use it — it saves storage space.
But the problem is that outside the Apple ecosystem, support for HEIC is basically poor:
- Chrome only started partial support at the end of 2020 (and it still requires OS-level HEVC decoding)
- Firefox does not support it
- Most backend image-processing libraries do not support it by default
- Older versions of Windows' built-in photo viewer cannot open it
So the reality is: an iPhone user takes a photo, uploads it to your site, your frontend does not recognize the format, the backend does not either, and then everything breaks.
The specific problem I ran into
Our mini-program used Canvas for image preview and cropping. The flow was like this:
- The user selects a file
FileReaderreads it as a DataURL- It is assigned to an
Imageobject - It gets drawn onto the Canvas for cropping
JPEG and PNG went through this flow without any issues. But the HEIC file failed at step 3 — the Image object's onerror fired, because the browser could not decode HEVC.
At the time I checked the console and saw something like net::ERR_UNKNOWN_URL_SCHEME or some other messy error. In short, the browser said "I don't recognize this thing."
Several solutions
Server-side conversion
The most straightforward idea is to convert HEIC to JPEG on the server. Node.js has the heic-convert library, and usage is simple:
const convert = require('heic-convert');
const result = await convert({
buffer: inputBuffer,
format: 'JPEG',
quality: 0.85,
});
But this library's underlying dependency is libheif, and installing it requires compiling C++ dependencies. It works fine in Docker, but running directly in cloud functions (like AWS Lambda or Alibaba Cloud Function Compute) is more troublesome — you have to build the image yourself.
Also, the user first uploads the HEIC to the server (maybe 5-8MB), then the server converts it, then the JPEG is sent back. The whole chain is long, and the user's waiting time is longer.
Client-side WASM decoding
Later I found a JavaScript library called heic2any that uses WebAssembly to decode HEIC in the browser. It does not require the server at all; it directly converts HEIC to a JPEG or PNG Blob on the client.
import heic2any from 'heic2any';
const jpegBlob = await heic2any({
blob: heicFile,
toType: 'image/jpeg',
quality: 0.85,
});
The advantage of this approach is that it eliminates the step of uploading a large file — the user selects the file in the browser, it gets converted locally to JPEG, and then the compressed file is uploaded.
The disadvantage is that the WASM decoder itself is about 600KB in size (roughly 200KB after gzip), so loading it once is noticeable. My way around it was lazy loading — only load the decoder when the file type is detected as HEIC.
Online tools as an emergency measure
Sometimes it is not your own product that needs to handle HEIC, but a temporary scenario: an operations colleague takes some product photos on an iPhone and needs to convert them to JPEG to upload to a backend system. The backend system does not support HEIC uploads, and the colleague does not know how to use Photoshop to convert formats.
In this case I usually go straight to an online tool. I have tried several: some upload files to a server for processing (which I was not comfortable with, since these were product photos), and some were very slow. Eventually I settled on the page glazlo.com/heic-to-jpg — it converts locally in the browser and does not upload files. The operation is also simple: select a file, wait for the conversion, download the JPEG, a few seconds per image.
The page's design is restrained — no ad pop-ups, no "register to download unlimited" tricks. You select the file and get the result directly. For someone who uses it occasionally, it is quite suitable.
The final solution
For our mini-program, we ended up with a dual strategy of client-side WASM decoding plus a server-side fallback:
async function handleImageUpload(file) {
// Detect HEIC
if (file.type === 'image/heic' || file.name.endsWith('.heic')) {
try {
// Prefer client-side conversion
const { default: heic2any } = await import('heic2any');
const jpegBlob = await heic2any({
blob: file,
toType: 'image/jpeg',
quality: 0.85,
});
return new File([jpegBlob], file.name.replace(/\.heic$/i, '.jpg'), {
type: 'image/jpeg',
});
} catch (e) {
// Client conversion failed, upload as-is and let the server handle it
console.warn('HEIC client-side conversion failed, falling back to server', e);
return file;
}
}
return file;
}
On the server side, we used heic-convert as a fallback layer, so if client-side conversion fails (some older browsers have issues with WASM support), the server can still catch it.
After going live, the HEIC white-screen problem basically disappeared. Occasionally users still reported slow uploads, but investigations showed it was a network issue, unrelated to HEIC.
Lessons learned
- Never assume the format of the images users upload. You think it is only JPEG and PNG? Users will give you HEIC, AVIF, even TIFF.
- Do not rely only on MIME type for file type detection.
file.typemay return an empty string in some browsers and systems. To be safe, also check the file extension and the file header (magic number). - HEIC has two MIME type spellings:
image/heicandimage/heif. You need to check both. - Client-side HEIC decoding is not fast. A 4000x3000 photo might take 3-5 seconds to convert on a phone, so remember to add a loading state so users do not think the page is stuck.
Image format compatibility is neither a huge nor a tiny problem. But once your product is aimed at ordinary users (rather than only developers), HEIC is an unavoidable hurdle. Handle it early, rather than being scolded by users after launch.
Content generated by AI