Most image tools start with a simple idea: upload a file, send it to a server, process it, and return a download.
That is a reasonable default. It is also more infrastructure than many common image tasks need.
I built Piclume as a lightweight image tool for compression, conversion, and resizing. The architectural decision at its core is deliberately simple:
Process an image in the browser when the browser can do the job reliably. Use a server only when compatibility or output control makes that necessary.
This is not a claim that every image should stay local. HEIC, AVIF, and deep PNG compression are real compatibility and quality-control problems. The goal is to make the boundary explicit instead of pretending that one processing path is right for every file.
The constraints I wanted to keep
The first version of Piclume has intentionally narrow product boundaries:
- no account or dashboard;
- no persistent user file library;
- no background job queue;
- no database required for a normal image job;
- a direct path from upload to download.
Those constraints are product decisions, but they also shape the architecture. If a JPG can be decoded, resized, re-encoded, and downloaded in a browser, uploading it to a server by default adds latency, transfer cost, and a privacy question without improving the result for that user.
At the same time, a local-only promise would create a different kind of failure. Browser support for image inputs and exports is uneven, and browser canvas APIs do not provide the same encoder control for every format. A useful tool needs a compatibility path.
The design: one interface, two processing engines
The user should not have to understand the whole architecture before compressing an image. They choose a file and an output option; the application selects an engine and shows the resulting path as Browser, Server, or Browser + server for a batch.
The routing rule is intentionally conservative:
- Accept browser-first processing only for inputs the browser can decode reliably.
- Check that the requested output format is supported by the current browser.
- Keep workflows with weak browser-side control on the server path.
- If local processing fails at runtime, retry through the server path.
Here is the essential shape of that decision:
export async function processImageFromClient(options: ProcessImageOptions) {
if (shouldPreferBrowserProcessing(options)) {
try {
return await processImageInBrowser(options);
} catch (error) {
console.warn("Browser processing failed; using the compatibility path.", error);
}
}
return processImageOnServer(options);
}
The important detail is the fallback. Capability detection is useful, but it is not a guarantee. A file can still fail to decode, a browser can behave differently from another browser, or a device can run into memory pressure. A browser-first design should degrade into a useful result, not an opaque error message.
What happens in the browser
For common JPG, PNG, and WebP inputs, Piclume uses the browser's image decoding and canvas APIs.
- Try
createImageBitmap()so image orientation is handled during decoding. - Fall back to an
HTMLImageElementwhen that route is unavailable. - Draw into a canvas at the requested output dimensions.
- Use high-quality image smoothing for resize operations.
- Export with
canvas.toBlob()and immediately offer the resultingBlobas a download.
That makes several useful workflows local by default: common JPG and WebP compression, JPG/PNG/WebP conversions that the browser can export, and many resize requests.
The browser path is not just about avoiding a network request. It keeps the interaction fast, makes a no-account workflow practical, and gives users an understandable answer to “where did my file go?”: for eligible jobs, it did not leave the browser.
Why the server still exists
The server is not a backup plan I hope never runs. It is a first-class compatibility layer.
Piclume sends selected jobs to a small Next.js route handler backed by Sharp. This path is used for formats or operations where browser behaviour is weaker or less controllable—for example, HEIC/HEIF input, AVIF-related compatibility paths, and PNG compression workflows where canvas export is not the right optimization tool.
The server endpoint accepts one file, processes it, and returns the binary result directly. It does not create a public result URL or a user history. The response also uses Cache-Control: no-store.
return new NextResponse(new Uint8Array(processed.buffer), {
headers: {
"Content-Type": processed.mimeType,
"Content-Disposition": `attachment; filename="${filename}"`,
"Cache-Control": "no-store",
},
});
For HEIC and HEIF, the compatibility work can be more involved. Sharp is tried first; on the deployment environment used by Piclume, a macOS sips fallback can help decode files that hit known support limitations before the result continues through the normal processing flow.
This is why I avoid the slogan “everything is processed locally.” It would be simpler copy, but it would be wrong. Accurate processing-path copy is part of the product.
Keep the processing path visible
Hybrid systems become confusing when their behaviour is invisible. If a user assumes a file stays local but it needs a server-side compatibility path, that distinction matters.
The workspace therefore surfaces the processing engine used for the completed job. In a mixed batch, the summary can say Browser + server instead of hiding the fact that different files took different routes.
That label also helps debugging. If a user reports a failed conversion, the engine is useful context. If browser processing becomes unreliable for a particular format or device class, it becomes measurable instead of anecdotal.
What I intentionally did not build yet
It is tempting to respond to every format edge case with more platform infrastructure: accounts, stored assets, queues, a public API, and long-running jobs.
For this stage, that would obscure the main product loop. The current priority is to make the existing upload → process → download loop reliable, observable, and honest about its trade-offs. More advanced batch workflows and metadata-removal tools can reuse the same browser-first/compatibility-path model later.
The takeaway is not that browser processing replaces server-side image tooling. It is that the browser is often a capable first processor—and a good architecture treats it as one.
If you want to try the result, Piclume's image compressor shows the same browser-first and server-fallback approach in the product.

Top comments (0)