Skip to content

Client-side image compression before upload to reduce mobile upload time #100

@JanMikes

Description

@JanMikes

Problem

Users on slow mobile connections experience 499 errors (client disconnects) when submitting the puzzle-add form with photos. Analysis of production Traefik logs (2026-03-28) shows:

  • One user made 16 consecutive failed attempts — all 499 status codes
  • Upload sizes: 1.5–6MB (phone photos)
  • Total request duration: 11–73 seconds
  • At ~1 Mbps mobile upload speed, a 5MB photo takes ~40 seconds just for the upload
  • Server processing is only ~1-2 seconds — the client upload dominates 80-97% of the total time

The root cause is that modern phone photos (12MP+ JPEGs) are 3-6MB, and users on slow mobile connections can't upload them fast enough before the browser/user gives up.

Proposed Solution

Use the browser's Canvas API to resize and compress images before upload:

  1. When user selects a photo in the form's file input
  2. JavaScript reads the image into a Canvas element
  3. Resize to max 2000px (matching server-side ImageOptimizer max dimension)
  4. Export as JPEG at quality 0.85
  5. Replace the file input with the compressed blob

Expected impact

  • Phone photo: 4032x3024 JPEG (5MB) → 2000x1500 JPEG (300-500KB)
  • Upload time at 1 Mbps: ~40 seconds → ~3 seconds

Considerations

  • Mobile CPU: Canvas resize of a single 12MP image takes <2 seconds even on older phones. Much faster than waiting 40+ seconds for upload.
  • HEIC handling: iOS Safari converts HEIC→JPEG when uploading via file input, so Canvas receives JPEG.
  • Fallback: If Canvas compression fails (rare), submit the original file as-is.
  • Quality: 2000px at 85% quality is identical to what the server-side ImageOptimizer produces. imgproxy handles all display thumbnailing anyway.
  • Both photo fields: Apply to both puzzlePhoto and finishedPuzzlesPhoto inputs.
  • Memory: Use URL.createObjectURL() + Image element instead of FileReader to reduce memory pressure. Call URL.revokeObjectURL() after use.

UX

Consider showing a brief "Compressing..." indicator while the Canvas processes, though it should be fast enough (<2s) that a simple spinner suffices.

Related changes already done

  • ImageOptimizer now uses JPEG size hint for faster server-side decoding, auto-orients, strips EXIF, and compresses at quality 85
  • submit_prevention_controller now keeps the submit button disabled until turbo:submit-end (was re-enabling after 10s, causing users to re-submit and cancel in-flight uploads)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions