Build a TinyPNG alternative
Build a TinyPNG alternative in the browser. Drop images, compress them, download them. No accounts, no CDN. Originals never leave the device.
You can build a TinyPNG alternative that lives in the tab. TinyPNG Pro is a drop zone. The catalog line is “Drop image. Make image smaller. Fin.”
You pay $25–$39 a month for it. That is $300–$468 a year. The basic replacement is 60 minutes: drop images, compress them, download them.
I would ship this if I still rented a quality slider. It is marked Tonight. The verdict on the card is “Embarrassingly easy”.
Yesterday I wrote about how Notion works. That was a teardown. Today we are back on a regular backlog topic, a compressor that never leaves the tab.
What you need to build a TinyPNG alternative
The TinyPNG Pro page splits the work into three rows.
The first row is the one you want tonight. Drop images, compress, download. A list of files, a smaller copy of each, a button.
The second row is 3 hours. Quality controls, a side-by-side preview, a zip export. Those can wait until you have compressed a real batch.
The third row is a week. A public API, CDN delivery, CMS plugins, accounts. The table labels that row as the part you should skip.
If you tell an agent to “clone TinyPNG”, it aims at the third row. Use the prompt on the page. It is written for a useful v1, and you still have to keep it on the first row.
This one is different from Bitly or Linktree. Those need a URL and a bit of storage. The capability line here is Astro, Alpine, and the browser. No database. No login.
Here is the shape of the data you are building. Nothing else has to exist at 60 minutes.
const job = {
id: 'job-14',
filename: 'hero-home.jpg',
width: 2400,
height: 1350,
originalBytes: 1843200,
quality: 0.82,
outputType: 'image/webp',
state: 'queued',
resultBytes: null,
resultUrl: null,
}
You drop hero-home.jpg. The tab encodes a smaller file. You download hero-home.min.webp. Refreshing the page throws the work away, and that is fine. The originals never left the machine.
Who a TinyPNG alternative is for
The audience line on the page is specific. A designer compressing batches of web images.
The goal is to compress common formats locally and make the savings obvious. Tonight the done state is one smaller file you can put on a site, plus a number that shows how many bytes you cut.
If you need a public compression API, or a WordPress plugin that rewrites every upload, keep paying. That is the honest rebuild. The site puts it at a week on purpose. TinyPNG’s developer API is that product: you POST bytes to api.tinify.com/shrink and pull the result. Build the drop zone if you are the one dropping files.
A framed screenshot for a launch tweet is a different job. That is Shots.so, $10–$20 a month, about 2 hours. Crop and annotate is Photoshop, about 12 hours. Build this if the file is already finished and only the bytes are the problem.
Read the non-goals before you copy anything
Open the guide. Read the audience and the non-goals before you touch the prompt.
The non-goal is the useful sentence: do not add accounts, cloud storage, billing, or an image CDN.
The guardrail sits next to it. Process images in the browser by default so originals never leave the device.
Then pick the stack you already write. Every prompt ships in four flavors: AHA (Astro, HTMX, Alpine.js), Next.js, Laravel, and Rails. The AHA version for this product uses Astro, Alpine, and browser APIs. This site is an Astro app on Cloudflare, so that is the path I default to. The scope matters more than the framework.
Copy the prompt. Before you run it, add one extra line at the top: ship the 60-minute row only. Drop, compress, download. Stop.
Walk the first TinyPNG journey
The prompt names three end-to-end journeys. Tonight you need the first one.
You drop a mixed batch. Each file validates and compresses in the tab. You compare original bytes and result bytes. You download the ones that worked.
The screens the prompt asks for are a workspace, an image row, an inspector, and a completion state. For 60 minutes you can live with the first two. The inspector and the zip belong to the 3-hour row.
The workspace is the whole app. A drop zone, a file picker, a privacy line that says the files stay local, a batch list, a running total of bytes saved, clear-all. The image row is the unit of work: thumbnail, formats, dimensions, original bytes, result bytes, percentage saved, state, remove, download.
The data model on the page is local ProcessingJob and ImageResult objects only. Tonight those can be one object.
One more rule from the prompt, and it is a good one. Never replace the original file in place. Write a new name. hero-home.jpg becomes hero-home.min.jpg, or hero-home.min.webp if the format changed.
Sniff the file before you decode it
Agents trust the extension and the MIME type the OS invented. People rename a PDF to hero-home.jpg. A 100-megapixel phone dump will crash the tab if you decode it first and ask questions later.
The mini-prompt on the page is “The 80-megapixel photo that must not crash the tab”. Tonight, read the first bytes. Accept JPEG, PNG, and WebP. Reject everything else with a reason on that row. The rest of the batch keeps going.
function sniffImage(bytes) {
if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
return 'image/jpeg'
}
if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) {
return 'image/png'
}
if (
bytes[0] === 0x52 &&
bytes[1] === 0x49 &&
bytes[2] === 0x46 &&
bytes[3] === 0x46 &&
bytes[8] === 0x57 &&
bytes[9] === 0x45 &&
bytes[10] === 0x42 &&
bytes[11] === 0x50
) {
return 'image/webp'
}
return null
}
sniffImage(new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))
//image/png
Then enforce a pixel budget. The mini-prompt uses 80 megapixels. An 80-megapixel decode in RGBA is over 300 MB before you have encoded anything. I would refuse that file with a sentence on the row. Do not silently downscale tonight.
const MAX_PIXELS = 80000000
async function compressJpeg(file, quality) {
const bitmap = await createImageBitmap(file)
const pixels = bitmap.width * bitmap.height
if (pixels > MAX_PIXELS) {
bitmap.close()
throw new Error(`Too many pixels: ${pixels}`)
}
const canvas = document.createElement('canvas')
canvas.width = bitmap.width
canvas.height = bitmap.height
canvas.getContext('2d').drawImage(bitmap, 0, 0)
bitmap.close()
const blob = await new Promise((resolve, reject) => {
canvas.toBlob((result) => {
if (!result) {
reject(new Error('Could not encode the image'))
return
}
resolve(result)
}, 'image/jpeg', quality)
})
return blob
}
That is canvas JPEG, not TinyPNG’s encoder. Say that to yourself before you cancel anything. For a blog hero and a folder of screenshots, a quality of 0.8 is enough. You will not match their exact quantization. You will get a file you can ship.
The catalog mentions WebCodecs when the browser has them. Use canvas.toBlob tonight. Probe WebP once at startup and hide that output if toBlob returns a different type.
Be careful with object URLs. Create one for the thumbnail and one for the result. Revoke both when the row is removed or the batch is cleared. Never send image bytes to analytics or a server.
Name the download so you cannot overwrite the original
Two source files named hero-home.jpg in one batch must not write the same output. The 3-hour zip has a suffix rule for that. Tonight you still need a name the browser can save.
function outputName(filename, outputType) {
const dot = filename.lastIndexOf('.')
const stem = dot === -1 ? filename : filename.slice(0, dot)
const ext = outputType === 'image/webp'
? 'webp'
: outputType === 'image/png'
? 'png'
: 'jpg'
return `${stem}.min.${ext}`
}
outputName('hero-home.jpg', 'image/webp')
//hero-home.min.webp
Preserve aspect ratio. The 3-hour inspector clamps resize to the source size unless the user overrides. Tonight you do not resize at all. Same pixels, fewer bytes. Show original bytes, result bytes, and the percentage on the row. Without those numbers you cannot tell whether the encode did anything.
What not to build in a TinyPNG clone
Leave out accounts, a login wall, cloud storage, billing, and anything that looks like an image CDN.
A public /shrink endpoint is the week-long row. So is a WordPress plugin.
If the agent starts an upload to R2 “so the batch survives a refresh”, stop it. Paste the non-goal again. The completion screen already reminds you that refreshing discards local work.
Google’s Squoosh is a public, open-source compressor that already lives in the tab. Use it as a UI reference if you want. Do not paste their encoder stack into a 60-minute v1.
The 3-hour row, after you compress a real folder
Use the compressor as the real tool for a week. Then look at the second estimate.
Quality and format on the selected image. A split preview. A zip of the whole batch.
The preview mini-prompt is “A before-and-after slider that tells the truth”. Both halves share one zoom and one pan. At 100 percent you compare real pixels, not a CSS-smoothed thumbnail.
The zip mini-prompt is “Build the ZIP yourself, byte by byte”. Store mode, because the images are already compressed. Deterministic names, and a numeric suffix when two photo.min.jpg entries would collide.
The worker-pool mini-prompt is “Compress 200 images without freezing the tab”. Bound the pool, queue the rest, fail one file at a time. Cancel must discard a result that lands late.
That is the solid v1. The catalog says 3 hours. Stop there.
The week-long row is the API, the CDN, the CMS plugin, and accounts. You do not need that.
Host it, compress the next batch, cancel
The product pages put hosting this way: a focused replacement costs your build time plus close-to-zero hosting.
This one can be a static page on a subdomain you already own, squish.flaviocopes.com, or a path next to a site you already deploy. There is no database to back up.
Use it for a week as the real tool, not as a demo. If you miss a zip or a split preview, add them from the 3-hour row. You will not miss a CDN.
Then cancel TinyPNG Pro.
A page of links is a different job. That is Linktree, $9–$24 a month, about 30 minutes. The Linktree walkthrough is that post. A one-page site is Carrd, $2–$4 a month, about 2 hours.
If you want another Tonight card after this one, the audit is still the index. Shots.so is about 2 hours and stays in the browser too.
Start now
Go to Build your own TinyPNG Pro. Copy the AHA prompt, or the stack you already write.
Tell the agent the first estimate only: drop images, compress, download. Sixty minutes.
Drop one real hero image. Put the smaller file on a page tonight.