Lynxz Blog Lynxz Blog
build-in-public tools javascript

I Built a Free OG Image Generator That Runs 100% in Your Browser (No Upload, No Account)

The full story of building Lynxz Tools — why I went 100% client-side, what the competition charges, and the real numbers behind a $0 product.

Lynxz ·

Every time you share a link on WhatsApp, Facebook, or X, the platform shows a preview image. Make it badly and your link gets ignored. Make it well and people actually click.

Most tools that generate these “Open Graph” images make you upload files to their server, create an account, or pay. So I built one that does neither: Lynxz Tools’ OG Image Generator renders everything in your browser using Canvas, and it’s free forever.

Here’s the full story — including the market research that shaped it and the mistakes I hit along the way.

Why 100% client-side?

The first design decision was the most important one: your files never leave your device.

Most image tools work like this: you upload to their server, their server processes it, you download the result. That means the operator pays for servers, bandwidth, and storage — costs that eventually get passed to you as paywalls, ads, or upload limits.

A client-side tool flips the model:

  • Processing happens in your browser via JavaScript and the Canvas API
  • Hosting is just static files — free forever on Cloudflare Pages
  • There is no per-user server cost, so there’s no reason to ever charge

The practical consequence: I can promise “free forever” and actually mean it. And users get something they rarely get from web tools anymore — their files never touch anyone’s server.

What the research actually showed

Before writing code, I researched the competitive landscape. The findings shaped every decision:

The pricing floor is $5. OGImagen, a direct competitor, charges $5 one-time for its Starter tier and $29/month for Pro. That’s the proven willingness-to-pay point in this niche.

Free tiers are limited by design. TinyPNG’s free web tool caps sessions at 20 images and 5MB per file. Squoosh (Google’s open-source compressor) handles one image at a time with no batch. iLoveIMG’s free tier limits compression to 30 operations.

The big all-in-one toolboxes monetize with ads. TinyWow offers 200+ free tools funded by banner ads. 10015.io is free with Chrome/Firefox extensions. Nobody in this space combines “free + no ads + no upload + bilingual” — that’s the gap.

Platform fees eat micro-prices. This one surprised me. On Gumroad, a $5 sale nets roughly $3.56 (10% + $0.50 + payment processing). On Ko-fi, the same sale nets about $4.30 (5% + processing). That’s a ~21% difference on every micro-sale — which is why my future paid tier will go through Ko-fi, not Gumroad.

Indonesia traffic is Tier 3 for ads. If you monetize with ads, Indonesian pageviews pay $0.10–$2 RPM versus $5–50 for US traffic. English content changes the economics by 5–10x. That’s why this blog exists in English.

The technical build

The stack is deliberately boring: HTML, Tailwind CSS, vanilla JavaScript, Canvas 2D. No framework. No build step. Deploy is uploading a folder.

The renderer draws in a virtual 1200×630 coordinate space and scales to fit any canvas — the same code powers the main preview, the five template selector chips, and the sample card on the landing page. One renderer, everywhere.

Five templates: gradient, dark, light, bold, and dots. Each is a function that paints a background, a badge pill, wrapped title text, description, and site name. The fitTitle helper iterates font sizes down from 74px until the title fits in three lines — because a 60-character title that silently overflows is worse than useless.

function fitTitle(ctx, text, maxW, maxLines) {
  const sizes = [74, 66, 58, 52, 46, 40];
  for (const size of sizes) {
    const lines = wrap(ctx, text, `800 ${size}px ${FONT}`, maxW);
    if (lines.length <= maxLines) return { lines, size };
  }
  return { lines: wrap(ctx, text, '800 40px ' + FONT, maxW).slice(0, maxLines), size: 40 };
}

The bugs that taught me the most

The white flash. After launch, users reported a white screen when refreshing in dark mode. The cause: my theme script ran on DOMContentLoaded — after the browser had already painted the light theme. The fix is a tiny external script in <head> that sets the theme attribute before the first paint. Now it’s instant. If you ever build a themed site, learn from my mistake: theme must be applied pre-paint, not post-load.

The invisible content trap. My scroll-reveal animations used IntersectionObserver. When I tested in a headless environment, IO never fired — and content sat at opacity: 0, invisible forever. The lesson: any animation that hides content by default must have a fallback that reveals it no matter what. Mine now falls back to a scroll-based checker if IO delivers nothing within 1.5 seconds.

CSP vs. inline everything. I wanted a strict Content-Security-Policy — no inline scripts, no inline styles. That forced real discipline: dictionaries moved to external files, chip previews render through Canvas instead of style attributes. The result is a site where the browser’s security model actively verifies my code does what I claim.

The numbers so far

Honest numbers, because that’s the point of this blog:

  • Build time: about 3 days at 3–5 hours per day
  • Hosting cost: $0 (Cloudflare Pages free tier — static assets have unlimited bandwidth)
  • Fonts: self-hosted, so zero external requests — better privacy and faster loads
  • Revenue: $0. The tool is free. That’s the strategy, not a failure.

The strategy is a content engine: free tools earn trust and traffic → traffic feeds this blog → blog audience becomes the distribution for future paid products. One machine, multiple income streams.

What I’d do differently

Ship the second tool faster. The first tool took three days partly because I designed five templates and full i18n before shipping anything. The next tool (an image compressor) will ship with one template and English only, then iterate based on real usage.

Write the tests first. The white-flash and invisible-content bugs both existed because I tested “does it work on my machine” instead of “does it survive a refresh, a slow connection, and a broken browser API.”

Publish earlier. I had the OG generator working in a day but sat on it for two more days polishing. The polish mattered, but shipping v1 and iterating publicly would have compounded faster.

Try it

The tool is live at lynxz-tools.pages.dev — no account, no upload, no ads. If it saves you time, the blog has more builds documented the same way.

Next up: the image compressor, and the first revenue experiment. I’ll publish the numbers here either way.

Written by Lynxz. If this helped you, consider sharing it or supporting the free tools at lynxz-tools.pages.dev.