I got tired of Googling "JPG to PNG" and landing on sketchy websites that demand I upload my personal documents (ID scans, contracts) to a random server just to change a file format.
So, I decided to build my own.
The goal was simple: Zero Server Uploads. I wanted a tool that runs entirely in the browser memory, processes files instantly without lag, and handles bulk conversion without crashing the UI.
Here is how I built Secure Converter using React, Vite 6, and Zustand, and the lessons I learned along the way.
The Stack
Core: React + TypeScript
Build Tool: Vite 6
State Management: Zustand (for the upload queue)
Styling: Tailwind CSS (Clean, symmetrical layout)
Engine: HTML5 Canvas API (Native browser performance)
The Challenge: The "Serverless" Engine
The biggest hurdle was rethinking how conversion works. Usually, you upload a file POST /upload, the server runs ImageMagick, and sends back a URL.
To do this client-side, I had to leverage the browser's native Canvas API, but not in the way it's usually used for games or drawing.
The architecture acts as a pipeline:
1.Ingestion: Instead of the old, slow FileReader (which converts images to massive Base64 strings), I switched to createImageBitmap. This decodes the image data directly in the browser's GPU memory, making it near-instant.
2.The "Invisible" Canvas: I programmatically create a canvas element that never actually touches the DOM. It sizes itself to match the image, "paints" the bitmap data onto itself, and then... nothing. It just sits in memory.
3.Extraction: This is where the magic happens. I use the canvas's ability to export blobs to specific MIME types (like image/png or image/webp). Essentially, I'm asking the browser to "take a photo" of the canvas in a different format.
The result? A conversion pipeline that handles 5MB images in milliseconds, with zero network latency.
The Performance Trap: Infinite Loops
Building the engine was the easy part. Building a performant UI that handles a queue of 50 images without freezing the browser was where I hit a wall.
I chose Zustand for state management because it's lightweight, but I initially made a rookie mistake. I hooked my main component to the entire state object.
Every time a single image finished converting, the state updated. Because I was subscribed to the whole object, React saw a "new" object reference and triggered a re-render of the entire list. With 50 images updating status one by one, this caused a cascade of re-renders that looked like a memory leak.
The fix wasn't optimization—it was discipline. I had to refactor the entire state logic to use Atomic Selectors. By forcing components to listen only to the specific Boolean or array they needed, the UI went from "stuttery" to 60fps smooth, even during heavy batch processing.
The Business Pivot: AdSense vs. Reality
As a developer, I focused on the code. But as a product maker, I needed a business model.
My initial plan was Google AdSense. I quickly learned that AdSense hates "Single Page Tools." They flag them as "Low Value Content" because there are no 2,000-word articles to crawl. Plus, putting Google trackers on a "Privacy" tool felt hypocritical.
I realized that my users aren't looking for random shoes or credit cards; they are looking for Data Safety.
I pivoted to a Direct Affiliate Model. I ripped out the placeholder slots and designed custom "Native Cards" that promote privacy-focused tools (like VPNs and Password Managers). This aligns the revenue model with the product's mission. It feels less like an "Ad" and more like a "Tech Stack Recommendation."
The Result
The final product, Secure Converter, is now live.
It supports the modern web trinity (JPG, PNG, WebP) and handles Vector (SVG) files perfectly. It features a "Holy Grail" symmetrical layout that balances the utility with the monetization features without cluttering the workspace.
Most importantly, it keeps its promise: You can load the page, disconnect your Wi-Fi, and convert confidential documents all day long. Nothing ever leaves your machine.
Check out the live demo here:
Secure Converter
View the Source Code:
Github Repo
Top comments (0)