cloudinary / cloudinary_npm

Cloudinary NPM for node.js integration
629 stars 322 forks source link

Unexpected token < in JSON at position 0. Status code 500 in Nextjs 13 production build (App router) #633

Open DongnutLa opened 1 year ago

DongnutLa commented 1 year ago

Describe the bug in a sentence or two.

Unexpected token < in JSON at position 0. Status code 500 in Nextjs 13 production build (App router)

Issue Type (Can be multiple)

[ ] Build - Can’t install or import the SDK [ ] Babel - Babel errors or cross browser issues [ ] Performance - Performance issues [X] Behaviour - Functions aren’t working as expected (Such as generate URL) [ ] Documentation - Inconsistency between the docs and behaviour [ ] Incorrect Types - For typescript users who are having problems with our d.ts files [ ] Other (Specify)

Steps to reproduce

Create a route src/app/api/upload/route.ts and use the _uploadstream method to upload any image

//src/app/api/upload/route.ts 

import { NextRequest, NextResponse } from "next/server";
import { UploadApiResponse, v2 as cloudinary } from "cloudinary";

const cloud_name = process.env.NEXT_CLOUDINARY_CLOUDNAME;
const api_key = process.env.NEXT_CLOUDINARY_API_KEY;
const api_secret = process.env.NEXT_CLOUDINARY_API_SECRET;

cloudinary.config({
  cloud_name,
  api_key,
  api_secret,
});

export async function POST(request: NextRequest) {
  const data = await request.formData();
  const image = data.get("file") as File;

  const bytes = await image.arrayBuffer();
  const buffer = Buffer.from(bytes);

  const response = await new Promise<UploadApiResponse>((resolve, reject) => {
    const stream = cloudinary.uploader.upload_stream({}, (err, res) => {
      if (err) reject(err);

      resolve(res!);
    });

    stream.write(buffer);
    stream.end();
  });

  return NextResponse.json({
    message: "File successfully uploaded",
    url: response.secure_url,
    id: response.public_id,
  });
}

Run next dev command and send a request to http://localhost:3000/api/upload with the image. This works fine

Now, run next build && next start and send same request to the same url and server will throw an HTTP 500 error:

Error screenshots

Screenshot 2023-10-07 at 7 56 47 PM

Browsers (if issue relates to UI, else ignore)

[ ] Chrome [ ] Firefox [ ] Safari [ ] Other (Specify) [ ] All

Versions and Libraries (fill in the version numbers)

Cloudinary_NPM SDK version 1.41.0 Node - 18.15.0 NPM - 9.6.4

Config Files (Please paste the following files if possible)

// package.json
{
  "name": "cloudinary-test",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "cloudinary": "^1.41.0",
    "next": "13.5.4",
    "react": "^18",
    "react-dom": "^18"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "typescript": "^5"
  }
}

Repository

https://github.com/DongnutLa/cloudinary-next13

colbyfayock commented 1 year ago

Hey @DongnutLa have you tried passing the buffer into the end method? I'm not terribly familiar with the stream API but it's worked for me before. Here's a svelte example https://github.com/cloudinary-community/cloudinary-examples/blob/main/examples/sveltekit-image-upload/src/routes/%2Bpage.server.js#L34

DongnutLa commented 1 year ago

Hey @DongnutLa have you tried passing the buffer into the end method? I'm not terribly familiar with the stream API but it's worked for me before. Here's a svelte example https://github.com/cloudinary-community/cloudinary-examples/blob/main/examples/sveltekit-image-upload/src/routes/%2Bpage.server.js#L34

Yeah, I have already tried that, but I'm getting the same error

wissam-khalili commented 1 year ago

Hi @DongnutLa,

Perhaps you can also try something along the lines of:

let cloudinary = require("cloudinary").v2;
let streamifier = require('streamifier');

let uploadFromBuffer = (req) => {

   return new Promise((resolve, reject) => {

     let cld_upload_stream = cloudinary.v2.uploader.upload_stream(
      {
        folder: "foo"
      },
      (error: any, result: any) => {

        if (result) {
          resolve(result);
        } else {
          reject(error);
         }
       }
     );

     streamifier.createReadStream(req.file.buffer).pipe(cld_upload_stream);
   });

};

let result = await uploadFromBuffer(req);

You can find more examples here as well.

Hope you find it useful. Regards, Wissam

colbyfayock commented 1 year ago

@DongnutLa i wasn't quite able to get your repo moving "as is" since there was no UI to work from, however

and with that i was able to successfully post an image file using formdata from the client

see this PR i set on your reproduction repo: https://github.com/DongnutLa/cloudinary-next13/pull/1/files

colbyfayock commented 1 year ago

oh and important to note, i was never able to reproduce the error you were seeing for some reason...

however i was getting:

[UPLOAD ERROR] TypeError: Cannot read properties of null (reading 'split')

which i think is related to the mimetype code, hence me commenting it out

DongnutLa commented 1 year ago

Hi @DongnutLa,

Perhaps you can also try something along the lines of:

let cloudinary = require("cloudinary").v2;
let streamifier = require('streamifier');

let uploadFromBuffer = (req) => {

   return new Promise((resolve, reject) => {

     let cld_upload_stream = cloudinary.v2.uploader.upload_stream(
      {
        folder: "foo"
      },
      (error: any, result: any) => {

        if (result) {
          resolve(result);
        } else {
          reject(error);
         }
       }
     );

     streamifier.createReadStream(req.file.buffer).pipe(cld_upload_stream);
   });

};

let result = await uploadFromBuffer(req);

You can find more examples here as well.

Hope you find it useful. Regards, Wissam

It works in development mode, but in a production build fails

DongnutLa commented 1 year ago

@DongnutLa i wasn't quite able to get your repo moving "as is" since there was no UI to work from, however

  • I commented out the mimetype and filesize check
  • I updated const buffer ... to const buffer = new Uint8Array(arrayBuffer);
  • you don't have your console.error inside of the if ( err ) check so its going to show up regardless
  • i removed stream.write and instead am using stream.end(buffer)

and with that i was able to successfully post an image file using formdata from the client

see this PR i set on your reproduction repo: https://github.com/DongnutLa/cloudinary-next13/pull/1/files

I check your changes, but in production build it's still failing Did you built the app and tried with next start ? Because in development mode it works well, but in production I'm getting the same 500 error.

tamaracloudinary commented 1 year ago

@DongnutLa if that works in dev mode, try to search for differences between your production and dev environment setups.

colbyfayock commented 1 year ago

hey @DongnutLa i put together this example:

https://github.com/cloudinary-community/cloudinary-examples/tree/main/examples/nextjs-route-handlers-upload

i deployed it on Vercel and it worked as expected

because it didnt include the same options, I also created a separate test just to make sure

https://github.com/colbyfayock/test-nextjs-route-handlers-upload/blob/main/src/app/api/upload/route.ts#L16

i also had no problems with uploading through this mechanism

one thing i did hit was a similar error when trying to upload a file that was too big. this could come from a few different places

image

though notice the error response

serverless functions have a body size limit: https://vercel.com/guides/how-to-bypass-vercel-body-size-limit-serverless-functions

there are also file size limits depending on your plan from Cloudinary, but higher than that amount: https://support.cloudinary.com/hc/en-us/articles/202520592-Do-you-have-a-file-size-limit-#:~:text=On%20our%20free%20plan%2C%20the,also%20limited%20to%2010%20MB.

where are you deploying your application? the only other consideration is if you're using the vercel Edge runtime but i don't see any configuration in your example using such, as I believe the Node SDK wouldn't be supported there at this time