Skip to main content
← ~/projects
cat ~/projects/snapcompress/CASE.md

SnapCompress

Image tools that run in your browser: compress, convert, resize and crop without uploading anything

// 01

Case study

## The problem

Online compressors and converters usually ask you to upload the image to their server. For a phone photo, a scan or a screenshot with personal data, that is a lot to ask in exchange for a smaller file.

I wanted the same result without the image leaving the device and without an account. If no server processes the image, there is nothing to store and nothing to leak.

## Key decisions

  1. Everything happens in the browser

    Compression uses browser-image-compression; conversion to WebP and PNG uses WebAssembly codecs (jSquash); iPhone HEIC is decoded with heic-to; resizing and cropping use the Canvas API. The site says images never leave the device, and the architecture makes that true.

  2. Next.js 16 with React Compiler, on Vercel

    The server only handles the optional newsletter and the contact form; neither receives images. The newsletter routes are rate limited. Hotjar, Clarity and Umami load only after cookie consent; Google Analytics starts in denied consent mode.

  3. The RAW decoder is only a fallback

    Most DNG files, including iPhone ProRAW, open through their embedded JPEG preview. The full RAW decoder is kept for the ones without it, which are rare.

## What went wrong

  1. 641 passing tests and real bugs (review of 5 June 2026)

    The quality review found that the newsletter's scheduled job skipped authentication when the CRON_SECRET variable was missing, that the newsletter routes had no rate limiting, and that cropping distorted non-square images when rotated 90° or 270°. The review document itself marks all three as fixed.

  2. My documentation said something the code did not do

    A document claimed all three tools used Web Workers. That was false: as of 5 June 2026 only compression and JPEG conversion ran in a worker, while resizing and WebP and PNG conversion ran on the main thread. I fixed the document, not the behaviour.

  3. Turbopack hung on the RAW decoder (23 September 2026)

    The production build with Turbopack hung on libraw-wasm, the RAW decoder. The turbopackIgnore comment did not fix it. It spawns workers of its own for its thread pool. On 23 September 2026 I chose to build a single-threaded fork of the library, and the next day the production build moved to Turbopack.

## What I would do differently

  • I would decide up front what runs in a worker and measure it, instead of letting whichever library I pick decide.
  • I would test the production build with the real bundler before adopting a WebAssembly library that spawns its own workers.
  • I would add rate limiting and a missing-secret check when creating each server route, not in a later review.
  • I would document what the code does today, with a date, and not what I intend it to do.

## Figures, only measured and dated

641
unit tests passing at the quality review, along with type-check, lint and the production build · 5 June 2026

I publish no compression percentages: the saving depends on each image and I have not measured it. I have no usage figures either.

// 02

Stack and links