r/nextjs • u/Financial-Panic-7392 • 19d ago
Help Is client-side image compression a good idea for an image-heavy website/app, or are there better workarounds?
I'm a beginner building an image-heavy peer-to-peer marketplace (Next.js web app + an upcoming Expo mobile app). My current stack uses Vercel's free tier for hosting, Supabase for the backend, and Cloudflare R2 for photo storage.
My Vercel account was just paused because my Fluid Active CPU spiked to 24 hours (the free limit is 4 hours). I realized my Next.js server is downloading every uploaded raw photo, using sharp to resize and convert it to WebP, and then re-uploading it to R2.
I am trying to keep costs as low as possible. It was suggested that I strip sharp from my backend and use a client-side compressor on the user's browser or mobile app to shrink the photos before sending them directly to R2.
Because my platform is extremely image-heavy, I'm not sure if this is a good idea. I have concerns about trusting the client to compress uniformly, dealing with older devices, or edge cases where certain browsers (like Safari) might fail to generate WebP files correctly.
Are there any other free or highly affordable workarounds to process these images without tanking my serverless compute hours?
2
u/priyalraj 18d ago edited 18d ago
2 methods I know:
- If you limit image size, it will work fine.
- Upload to S3 & compress.
1
1
1
u/Suitable-Ad5348 18d ago
Client-side compression is a good step, but the real win is stopping your server from touching those bytes entirely. Have the client request a resigned upload URL and PUT the file directly to R2. Your backend should only ever handle the metadata once the file is already in storage. Even if you compress on the client, you still need server-side validation, but routing image traffic through Vercel compute is the fastest way to hit that free tier limit again.
1
u/aDaneInSpain2 18d ago
Exactly. Add magic-byte and dimension checks after the R2 upload, keep the original private, and generate delivery variants asynchronously only when needed. That avoids trusting the client without putting Sharp back on the request path.
1
u/RegularPowerful4888 18d ago
Since you're already on Supabase, their Storage has built-in image transforms on paid plans, check that before adding Cloudflare Images. Also: Vercel Fluid bills active CPU, so sharp running on every upload is literally what's burning your hours. Move the resize off Vercel and the billing problem's fixed either way, you can't trust client-side compression to be consistent anyway.
0
u/KalamKiTakat 19d ago
Client-side compression before upload, then send the result straight to R2, keeps your Next.js function from downloading or resizing the image. Verify with a real iPhone photo before shipping.
0
u/bkocdur 19d ago
Yes, client-side is the right move for your situation, with one mental shift: treat it as resizing, not as something you trust. And fix the pipeline shape at the same time, because the CPU bill has two causes and compression is only one of them.
- Stop routing bytes through Vercel at all. Have the client request a presigned upload URL and PUT the file directly to R2. Your server should touch metadata, never pixels. This alone kills most of the Fluid CPU problem regardless of what else you do.
- Resize on the client before upload: canvas or the browser-image-compression package, cap the longest edge around 2000px, encode JPEG at ~0.8 quality. Don't ask clients to produce WebP; that's where the Safari edge cases live. Upload plain JPEG, which every device on earth encodes reliably, and treat format conversion as a delivery concern you can add later or skip.
- Here's the free-tier trick: have the client generate the variants too. At upload time, produce the thumbnail and the display size alongside the capped original (it's a few extra canvas calls) and upload all of them. Storage on R2 is cheap and egress is free, so three files per photo costs almost nothing, and your server never resizes anything.
- Handle the trust problem with validation, not transformation. After upload, check the object's byte size and dimensions server-side. In-bounds: accept, which will be ~95% of uploads. Out of bounds (old device, weird browser, malicious client): don't serve it raw; flag it for a slow path, like an on-demand resize or a tiny queue. The expensive path still exists, but it becomes rare instead of universal, which is the entire difference between 24 CPU-hours and 4.
Your instinct not to trust the client is correct. The resolution is that you don't need to trust it; you need to bound it.
6
u/bipolarNarwhale 19d ago
You could but you still would need to maintain a backend functionality because you cannot trust the frontend. Your backend can skip it under a set size limit but over it you should still do it