Next.js Discord

Discord Forum

I get this error when trying to run a POST request for compressing images with Sharp

Answered
Sun bear posted this in #help-forum
Open in Discord
Sun bearOP
Is the path incorrect or is the error caused by something else ?

If the path is incorrect please let me know what the correct path should be instead.

Here is my code:

// components/user-profile-card.tsx
 try {
            const response = await fetch('../src/app/api/compress-image', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ file: base64File }),
            });


// src/app/api/compress-image/route.ts

import { NextResponse } from 'next/server';
import sharp from 'sharp';

export async function POST(request: Request) {
  const { file } = await request.json();

  if (!file) {
    return NextResponse.json({ error: 'No file provided' }, { status: 400 });
  }

  try {
    const compressedFile = await sharp(Buffer.from(file, 'base64'))
      .toFormat('jpeg', { quality: 80 })
      .toBuffer();

    return NextResponse.json({ compressedFile: compressedFile.toString('base64') });
  } catch (error) {
    console.error('Error compressing image:', error);
    return NextResponse.json({ error: 'Failed to compress image' }, { status: 500 });
  }
}
Answered by Anay-208
Summary:
- The issue was caused because sharp was running client side rather server side
- It was fixed by making the function server side

Recommendations:
- Use client side packages instead, as it could lead to some data transfer costs(if any) and increase in time to compress & upload(as the original file is being sent to server, and being returned)
View full answer

126 Replies

did you mean this by any chance:
fetch('/api/compress-image', {
the error is because ts 404 page and you are trying to json stringify it
@riský did you mean this by any chance: ts fetch('/api/compress-image', {
Sun bearOP
Risky I think I found what the issue was. So what I had to do was this:

I had to move the file here inside the locale folder, this kinda sucks cause I'm not sure if it's supposed to be here since this is a dynamic folder. I would've wanted it to be outside the locale folder, just under /src/app.
oh thats why you were ../
Sun bearOP
Then I had to add this line here:

 // Upload the compressed image to Supabase
              const { data, error } = await supabaseClient.storage
                .from('images')
                .upload(newFilePath, Buffer.from(compressedFile, 'base64'), {
                  cacheControl: '3600',
                  upsert: true,
                  contentType: 'image/jpeg', // I HAD TO ADD THIS LINE
                });
but you dont need tohave the src and app
why is compress image locale based anyway
and have you considered server actions for peak simpleness
@riský why is compress image locale based anyway
Sun bearOP
Yeah I don't get it either
It shouldn't be locale based
then put it in root, and use root selector for fetch :box_cat_hide:
@riský *and have you considered server actions for peak simpleness*
Sun bearOP
And should I make it an action instead ?
idk up to you
Sun bearOP
Instead of a POST request ?
well server action is post :)
@riský *and have you considered server actions for peak simpleness*
Sun bearOP
Then why did you asked me this if it's already a server action 😄
i meant as it does use post
but it does that behind scenes
as far as code is conserned one is just another async function (and nextjs makes it fancy with post req)
and then idk if you need to json stringify, when you can use formdata :)
also @Sun bear the fetch is relative to page it was called from in browser, not nextjs routing
Sun bearOP
Okay hold on I just moved it inside the app folder again.
yay
Sun bearOP
I get this error again now lol
what is the fetch code your using
Sun bearOP
 // Upload the compressed image to Supabase
              const { data, error } = await supabaseClient.storage
                .from('images')
                .upload(newFilePath, Buffer.from(compressedFile, 'base64'), {
                  cacheControl: '3600',
                  upsert: true,
                  contentType: 'image/jpeg',
                });

Do I need to upate something here ?
like is it fetch("/api/compress-image")?
Sun bearOP
Ohh hold on
@riský like is it `fetch("/api/compress-image")`?
Sun bearOP
  try {
            const response = await fetch(`/api/compress-image`, {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ file: base64File }),
            });
ye, no need to ../ many times when you can reference root and go off that
Sun bearOP
I'm not sure, when it was under the [locale] folder it worked
But outside it it gives that error
I can share the middleware code if necessary
@Sun bear I'm not sure, when it was under the [locale] folder it worked
yeah as its probably redirecting to /en because langs are fun
so in middleware either ignore the api dir, or idk
so like how its ignoring _next make it ignore api
Sun bearOP
Hmm so I added this inside the matcher:

export const config = {
  matcher: [

    // Exclude the image compression API route
    "/((?!api/compress-image|_next/static|_next/image|favicon.ico|sw.js|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
  ],
};

It still doesn't work
can you share your network tab
like is it redirecting with 307 or smth
@riský can you share your network tab
Sun bearOP
yeah 307
indeed redirecting
Sun bearOP
Also the profile image takes forever to load lol.
Like it took almost a minute.
And it's only 3.09 MB
server actions wont have this issue, but i havent used middleware much, so i dont know its fancyness
@riský *server actions wont have this issue*, but i havent used middleware much, so i dont know its fancyness
Sun bearOP
Okay so can you help me update the route.ts like you told me without POST ?
Also you said this "and then idk if you need to json stringify, when you can use formdata :)"
Let's do this too
// actions/compress-image.ts

import { NextResponse } from 'next/server';
import sharp from 'sharp';

export default async function CompressImage({ file }: { file: string }) {
  if (!file) {
    return { error: 'No file provided' };
  }

  try {
    const compressedFile = await sharp(Buffer.from(file, 'base64'))
      .toFormat('jpeg', { quality: 80 })
      .toBuffer();

    return { compressedFile: compressedFile.toString('base64') };
  } catch (error) {
    console.error('Error compressing image:', error);
    return { error: 'Failed to compress image' }
}


// components/user-profile-card.tsx
import CompressImage from "../actions/compress-image.ts"

 try {
   const response = await CompressImage({ file: base64File })
something like this should work... its freehanded and not very tested
@Sun bear Also you said this "and then idk if you need to json stringify, when you can use formdata :)"
how are you making the file base64, as its source kinda dictates how formdata can be used
I would appreciate if you'd just modify it like it needs to and remove the code I used for the POST action cause the file is quite complex so I don't know what to remove exactly and where to place things.
i havent really played with formdata and i think i should go to bed, i can try to fix tmr or maybe someone else can...
but i think event.target can be turned into formdata
and then you dont need the filereader madness
@riský i havent really played with formdata and i think i should go to bed, i can try to fix tmr or maybe someone else can...
Sun bearOP
Gotcha, well I'll try and follow your advice with the help of the AI in the meantime 😁
Thank you very much
yay sounds good
@riský but i think `event.target` can be turned into formdata
Sun bearOP
Risky I think I've managed to make it use FormData but it's still using the POST file
https://paste.ec/paste/C0n2UWBR#HhUYmdD025twoSHrANKykbbpxC7ZWrC7MVrfE8vqAzM

When you have some time can you have a look and confirm it it's indeed doing that and if it got rid of the "file reader madness" ? 😁

Thank you!
Claude says these are the changes it made but I want to confirm that's indeed what it did and if it followed your advice correctly.
and i see no usages of filereader now!
and i think the claude can tell you how to use it in api handler (server actions would be same way of accessing once you have the formdata)
Sun bearOP
Awesome! For some reason it didn't replace the code that uses that POST file, even though I told it to use the action file instead.

I have to wait 2 more hours until I can talk to it again 😁
But at least it got the rest of the instructions right, which is awesome!
i hope you can return formdata, and then have supabaseClient.storage use that too, as base64 lame/can get more confusing
actually it prob doesnt matter here
and to save bandwidth... why dont you just upload to supabase in the server action :)
Sun bearOP
I'll have to see when Claude replaces the code to use Action instead cause right now it's still using the POST file and it gives that 404 error because of the redirect. I also reverted my middleware to how it was since I'm not going to use that POST file anymore.
@riský and to save bandwidth... why dont you just upload to supabase in the server action \:)
Sun bearOP
Isn't that what it's currently doing ?
you are returning the image back to user to send to supabase
Sun bearOP
But it's only uploading the image one time right ? So how does that save bandwith if in the end it's only uploading it once ?
Also I don't understand what bandwith means exactly in this context, like bandwith for what ?
if you host on vercel, it will count both ingress and egress (input data and returned data)
Sun bearOP
Hmm but I have a hard time wrapping my head around it. So is the code currently doing something like this ?

User requests uploading image
Code sends image to server
Server compresses the image and sends image back to Client
Client then uploads image to Supabase
yes
Sun bearOP
I can't believe I guessed it lol
also i thought supabase had ways to resize and things anyway... https://supabase.com/blog/storage-image-resizing-smart-cdn#image-resizing
but making it store less on your server saves you storage used
@riský but making it store less on your server saves you storage used
Sun bearOP
So doing it through the server action instead is better right ?
idk up to you
one could say short term vs long term... but i would just say upload on server and avoid the client needing to do that
Sun bearOP
This is complicated stuff it seems. Like this whole bandwidth storage thing.
either that, or find some client side compression lib 😉
@Sun bear This is complicated stuff it seems. Like this whole bandwidth storage thing.
it is, and you prob dont need to fuss over it, i just think it would also be faster then going backwards and forwards with img
@riský it is, and you prob dont need to fuss over it, i just think it would also be faster then going backwards and forwards with img
Sun bearOP
So you'd suggest not involving the server at all during the image upload process right ?

But thing is I'm making one check if you look at the code, which is I'm checking if the profile id coresponds to the logged in user id, just in case someone tries to modify someone elses profile.

So my code needs to communicate with the server during this upload process anyway, isn't that right ?
that is a good idea, but i dont know supabase and what its perm system is like
like is it only userspace storage?
as i hope the creds the client has, cant modify data for everyone
@riský that is a good idea, but i dont know supabase and what its perm system is like
Sun bearOP
I'm not sure but they have lots of documentation about it here https://supabase.com/docs/guides/storage
But I don't know which section would answer that question
i stick to the less fancy things and be more barebones as at least i understand what im doing and have "it cheaper"
@riský as i hope the creds the client has, cant modify data for everyone
Sun bearOP
They have the following here hold on
Have a look
Sun bearOP
@riský It looks like it doesn't want to work. The code compiles but when I try to access My Profile I get this error
I found a thread on StackOverflow in which someone said Sharp can't be used in Client components
@Sun bear I found a thread on StackOverflow in which someone said Sharp can't be used in Client components
Can you send code of @/utilities/actions/compress-image
@Anay-208 Can you send code of `@/utilities/actions/compress-image`
Sun bearOP
// utilities/actions/compress-image.ts

import { NextResponse } from 'next/server';
import sharp from 'sharp';

export default async function CompressImage({ file }: { file: string }) {
  if (!file) {
    return { error: 'No file provided' };
  }

  try {
    const compressedFile = await sharp(Buffer.from(file, 'base64'))
      .toFormat('jpeg', { quality: 80 })
      .toBuffer();

    return { compressedFile: compressedFile.toString('base64') };
  } catch (error) {
    console.error('Error compressing image:', error);
    return { error: 'Failed to compress image' }
}
}
@Anay-208 add `"use server"` at the top
Sun bearOP
It loads now!
😮
And it seems like the compression is working too!
Summary:
- The issue was caused because sharp was running client side rather server side
- It was fixed by making the function server side

Recommendations:
- Use client side packages instead, as it could lead to some data transfer costs(if any) and increase in time to compress & upload(as the original file is being sent to server, and being returned)
Answer
Mark the above message as a solution
I haven't personally used any, however, you can either make use of the canvas element, or https://www.npmjs.com/package/browser-image-compression
I would recommend the npm package
Sun bearOP
Thank you very much, I'll check those out. Thank you to both you you! 🙏🏻