405 Cloudinary ONLY on production using Vercel - Nextjs
Please help, 2 weeks stuck. So far no answers works, no AI, nobody, no workaround, I have no idea where to look anymore.
Uploading images on development works perfect, on production 405. Add headers, take headers, change CORS, use Axios, change to fetch, clear cache, downgrade Nextjs, try different npm's etc... No idea on what to do next.
ON FRONT
const createImagesSrc = async (
imageFileToAdd: File[]
): Promise<AxiosResponse | null> => {
try {
const formData = new FormData();
imageFileToAdd.forEach((image) => {
formData.append("images", image);
});
const uploadResponse = await axios.post("/api/upload", formData, {
})
if (uploadResponse.status === 200) {
uploadResponse.data.uploadResponses.forEach(
(response: any, index: number) => {
imageSrc.push(response.secure_url);
}
);
return uploadResponse;
} else {
console.error("Failed to upload images");
return null;
}
} catch (error) {
console.error(error);
return null;
}
};
ON BACK
cloudinary.config({
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.API_KEY,
api_secret: process.env.API_SECRET,
});
export async function POST(request: Request) {
const data = await request.formData();
const files = data.getAll("images");
if (files.length === 0) {
return NextResponse.json({
success: false,
message: "No images to upload.",
});
}
const uploadPreset = "yfhyp9my";
const uploadResponses: any[] = [];
for (const file of files) {
// @ts-ignore
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);
const response = await new Promise((resolve, reject) => {
cloudinary.uploader
.upload_stream({ upload_preset: uploadPreset }, (error, result) => {
if (error) {
reject(error);
}
resolve(result);
})
.end(buffer);
});
uploadResponses.push(response);
}
return NextResponse.json({ success: true, uploadResponses });
}
NEXT.CONFIG.JS
const nextConfig = {
async headers() {
return [
{
// matching all API routes
source: "/api/:path*",
headers: [
{ key: "Access-Control-Allow-Credentials", value: "true" },
{ key: "Access-Control-Allow-Origin", value: "coolbananas.org, localhost:3000" },
{
key: "Access-Control-Allow-Methods",
value: "GET,OPTIONS,PATCH,DELETE,POST,PUT",
},
{
key: "Access-Control-Allow-Headers",
value:
"X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version",
},
],
},
];
},
images: {
remotePatterns: [
{
protocol: "https",
hostname: "res.cloudinary.com",
port: "",
pathname: "/jpsm83/image/upload/**",
},
{
protocol: "https",
hostname: "lh3.googleusercontent.com",
},
{
protocol: "https",
hostname: "restcountries.com",
},
],
},
};
ERROR
POST https://www.coolbananas.org/api/upload 405 (Method Not Allowed)
- AxiosError {message: 'Request failed with status code 405', name: 'AxiosError', code: 'ERR_BAD_REQUEST', config: {…}, request: XMLHttpRequest, …}
- code
- :
- "ERR_BAD_REQUEST"
- config
- :
- {transitional: {…}, adapter: Array(2), transformRequest: Array(1), transformResponse: Array(1), timeout: 0, …}
- message
- :
- "Request failed with status code 405"
- name
- :
- "AxiosError"
- request
- :
- XMLHttpRequest {onreadystatechange: null, readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
- response
- :
- config
- :
- adapter
- :
- (2) ['xhr', 'http']
- data
- :
- FormData {}
- env
- :
- {FormData: ƒ, Blob: ƒ}
- headers
- :
- AxiosHeaders {Accept: 'application/json, text/plain, */*', Content-Type: false}
- maxBodyLength
- :
- -1
- maxContentLength
- :
- -1
- method
- :
- "post"
- timeout
- :
- 0
- transformRequest
- :
- [ƒ]
- transformResponse
- :
- [ƒ]
- transitional
- :
- {silentJSONParsing: true, forcedJSONParsing: true, clarifyTimeoutError: false}
- url
- :
- "/api/upload"
- validateStatus
- :
- ƒ (e)
- xsrfCookieName
- :
- "XSRF-TOKEN"
- xsrfHeaderName
- :
- "X-XSRF-TOKEN"
- [[Prototype]]
- :
- Object
- data
- :
- ""
- headers
- :
- AxiosHeaders {access-control-allow-credentials: 'true', access-control-allow-headers: 'X-CSRF-Token, X-Requested-With, Accept, Accept-Ver…h, Content-MD5, Content-Type, Date, X-Api-Version', access-control-allow-methods: 'GET,OPTIONS,PATCH,DELETE,POST,PUT', access-control-allow-origin: 'coolbananas.org, localhost:3000', cache-control: 'public, max-age=0, must-revalidate', …}
- request
- :
- XMLHttpRequest
- onabort
- :
- ƒ ()
- onerror
- :
- ƒ ()
- onload
- :
- null
- onloadend
- :
- ƒ onloadend()
- onloadstart
- :
- null
- onprogress
- :
- null
- onreadystatechange
- :
- null
- ontimeout
- :
- ƒ ()
- readyState
- :
- 4
- response
- :
- ""
- responseText
- :
- ""
- responseType
- :
- ""
- responseURL
- :
- "https://www.coolbananas.org/api/upload"
- responseXML
- :
- null
- status
- :
- 405
- statusText
- :
- ""
- timeout
- :
- 0
- upload
- :
- XMLHttpRequestUpload
- onabort
- :
- null
- onerror
- :
- null
- onload
- :
- null
- onloadend
- :
- null
- onloadstart
- :
- null
- onprogress
- :
- null
- ontimeout
- :
- null
- [[Prototype]]
- :
- XMLHttpRequestUpload
- withCredentials
- :
- false
- [[Prototype]]
- :
- XMLHttpRequest
- status
- :
- 405
- statusText
- :
- ""
- [[Prototype]]
- :
- Object
- stack
- :
- "AxiosError: Request failed with status code 405\n at settle (https://www.coolbananas.org/_next/static/chunks/118-3737272d4a0dcc07.js:37:18624)\n at XMLHttpRequest.onloadend (https://www.coolbananas.org/_next/static/chunks/118-3737272d4a0dcc07.js:37:21902)"
- [[Prototype]]
- :
- Error
This is an issue only in PRODUCTION mode, development is fine. All "keys" been add to Vercel and Vercel is working properly once I can save all data to MongoDB but the images doesnt get create at Cloudinary. I tryed to update CORS at Cloudinary but seen to have no such option on their website. Very frustate, I got pretty bad stuck ;(
Best Answer
-
Using the SDK method
upload_stream
, there is an unhandled error when the method is invoked in abuild
NextJS environment. An error HTML page is being received as a response instead of a JSON data format.As an alternative to the use of
upload_stream
, the `upload` method of the Node SDK together with a Data URI can be used, for example:import { v2 as cloudinary } from "cloudinary"; import { NextResponse } from "next/server"; // Cloudinary config cloudinary.config({ cloud_name: "your_cloud_name", api_key: "your_api_key", api_secret: "your_api_secret", secure: true, }); export const POST = async (req) => { const data = await req.formData(); const image = await data.get("image"); const fileBuffer = await image.arrayBuffer(); var mime = image.type; var encoding = 'base64'; var base64Data = Buffer.from(fileBuffer).toString('base64'); var fileUri = 'data:' + mime + ';' + encoding + ',' + base64Data; try { const uploadToCloudinary = () => { return new Promise((resolve, reject) => { var result = cloudinary.uploader.upload(fileUri, { invalidate: true }) .then((result) => { console.log(result); resolve(result); }) .catch((error) => { console.log(error); reject(error); }); }); }; const result = await uploadToCloudinary(); let imageUrl = result.secure_url; return NextResponse.json( { success: true, imageUrl: imageUrl }, { status: 200 } ); } catch (error) { console.log("server err", error); return NextResponse.json({ err: "Internal Server Error" }, { status: 500 }); } };
As for the uploading of files in their byte array buffer format, we have raised an internal ticket for review and to provide the needed fix.
Can you try this different upload method?
2
Answers
-
AMAZING is working..... :D Thanks very much, you made my day
2 -
Saved my life.
Spend two day to handle this error with nextjs 14 and server action.
The nextjs-server-actions-upload example was working perfectly but when tried to implement in my project which was a little bit different it did not work.
And because I had an http-error 500 I did not find this post... I was desperate I start to try anything and found yours.
Thank you
0 -
Thank you. I just found this solutions and works well. I didn't know what was happening, you save my life.
1