Client-side media preflight can improve an upload experience, but it can also crash the page before the network request begins. A twelve-megapixel JPEG may be only four megabytes on disk and tens of megabytes after decoding. Creating several full-size canvases at once is enough to exhaust memory on older phones.
Preflight is policy, not editing
Decide what the browser must prove before transfer. Useful checks include file count, compressed byte size, declared type, readable dimensions, and a conservative duration limit for video. Avoid mandatory re-encoding unless the product truly needs it. Every transformation adds CPU time, memory pressure, battery use, and another failure mode.
Process one file at a time
A file picker may return twenty items. Do not decode all of them to build previews. Maintain a queue with one active decode on constrained devices and at most two on stronger ones.
for (const file of files) {
const result = await inspect(file);
renderResult(result);
await yieldToMainThread();
}
The UI can list filenames immediately while dimensions and thumbnails arrive progressively.
Prefer metadata over full pixels
Use createImageBitmap with resize hints when supported. It can decode away from the main rendering path and avoid a full-resolution canvas for a small preview.
const bitmap = await createImageBitmap(file, {
resizeWidth: 640,
resizeQuality: 'medium'
});
Always close bitmaps after drawing. Revoke object URLs when the component unmounts. Small leaks become large when guests select and remove files repeatedly.
Handle orientation and color carefully
Modern browsers generally honor EXIF orientation when decoding, but behavior differs across APIs and older engines. Test portrait photographs from real iOS and Android devices. Do not strip metadata silently if the original is supposed to remain untouched; upload the source file and treat the preview as disposable UI.
Color differences between the preview and exported original are usually less harmful than a destructive client-side conversion. Be explicit about whether the product preserves originals.
Video needs different limits
Reading a complete video into an ArrayBuffer simply to inspect duration defeats streaming. Create a temporary media element from an object URL, wait for metadata, then release it. Add a timeout because damaged files may never emit the expected event.
Do not generate multiple video thumbnails concurrently on mobile. Seek operations can trigger additional decoding and large temporary buffers.
Survive cancellation
Every preflight task needs an abort path when the user removes a file, picks a replacement, navigates away, or the event closes. Use an AbortController for your own async boundaries and check the signal between decode stages.
Report errors per file. One unreadable image should not discard nineteen valid contributions. Use specific messages: unsupported type, file too large, video too long, or preview unavailable but upload permitted.
Measure without collecting filenames
Track coarse outcomes such as decoded, rejected by size, preview timeout, and out-of-memory page recovery. Filenames and media contents are unnecessary for operational metrics.
I help build Gathmo and therefore have a commercial interest in reliable browser media handling. The Gathmo help centre describes product-specific behavior; the memory rules here apply to any mobile upload interface.
A good preflight pipeline is intentionally modest. It validates what the server needs, produces a small preview, releases resources promptly, and never risks the original merely to make the interface look faster.
Top comments (0)