I've been building Relahconvert — a free browser-based image toolkit — for about a month now. 37 tools, 25 languages, zero backend for most of it.
Sounds clean. It wasn't 😄
Here are 9 real mistakes I made and what actually solved them.
"I'll just use a library for that" → Canvas API
My first instinct for image processing was to reach for a library. Turns out the browser's Canvas API handles resize, crop, flip, grayscale, and more natively. No dependencies. No bundle size. Just pixels.
js
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, newWidth, newHeight);
canvas.toBlob(blob => downloadFile(blob), 'image/jpeg', 0.9);
I now use this for 90% of my tools."Dark mode needs JavaScript" → prefers-color-scheme
I built a whole JS toggle system before realizing the browser already knows what the user wants.
css@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1a1a;
--text: #f0f0f0;
}
}
Still kept the manual toggle — but the default is now instant and correct."I need a backend for file downloads" → URL.createObjectURL
I thought serving processed images required a server. Nope.
jsconst url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'converted.png';
a.click();
URL.revokeObjectURL(url);
Everything stays in the browser. No uploads. No server costs. Users love the privacy angle."Batch processing will kill performance" → Web Workers
Processing 25 images at once was freezing the UI. Web Workers run in a separate thread — the page stays responsive while the heavy lifting happens in the background.
jsconst worker = new Worker('process.js');
worker.postMessage({ imageData, format: 'webp' });
worker.onmessage = e => renderResult(e.data);
Game changer for batch tools."I need an API for compression" → canvas.toBlob quality param
I almost paid for a compression API. Then I noticed toBlob has a quality parameter.
jscanvas.toBlob(blob => save(blob), 'image/jpeg', 0.7);
0.7 gives you roughly 60-70% size reduction with barely visible quality loss. Free. Built-in. Already there."RTL Arabic will break my layout" → CSS logical properties
Supporting 25 languages including Arabic felt terrifying. Logical properties made it manageable.
css.tool-container {
margin-inline-start: 1rem;
padding-inline: 1.5rem;
}
One declaration. Works LTR and RTL automatically."I'll handle the API key in the frontend" → Cloudflare Workers
I exposed an API key in my frontend. Bots found it within days 😬
The fix: a Cloudflare Worker as a proxy. Your key lives server-side, requests go through the worker, users never see it.
jsexport default {
async fetch(request) {
const response = await fetch('https://api.remove.bg/v1.0/removebg', {
method: 'POST',
headers: { 'X-Api-Key': API_KEY },
body: await request.formData()
});
return response;
}
}
Lesson learned the hard way."Multi-language SEO is just hreflang tags" → it's not
I had hreflang conflicts with canonical tags for weeks and couldn't figure out why my impressions were dropping. Canonicals, sitemaps, and hreflang all need to agree with each other.
html
One wrong canonical and Google ignores your entire language setup."More tools = more traffic" → wrong
I built 37 tools thinking volume would win. But 37 unindexed tools is worth less than 5 indexed and ranking ones.
Now I ask before building: is this keyword searchable? Is the difficulty realistic for my domain authority? Does it solve a real problem?
Tools are only as valuable as the traffic they attract.
What I'm still figuring out
Supabase auth across 25 languages
Getting Google to index all 950 pages faster
Whether PDF to PNG is worth the complexity
Building in public is humbling. But the browser is genuinely more powerful than most tutorials let on — and that's what makes solo projects like this possible.
Top comments (0)