Next.js App Router生产环境Cloudinary文件上传500错误求助
Next.js部署Vercel后Cloudinary上传图片返回500错误排查与修复
问题描述
开发环境下Cloudinary电影海报上传功能正常,但部署到Vercel生产环境(moviehub-nextjs.vercel.app)后功能失效,返回500错误。控制台提示:
服务器返回无效JSON响应,状态码500,SyntaxError: Unexpected token '<',"<!DOCTYPE "... 不是有效JSON
Cloudinary上传结果打印为undefined,已确认.env配置正常,Cloudinary的DELETE操作、用户数据POST、电影数据PUT等其他功能均能正常运行。
原代码如下:
import { NextResponse, NextRequest } from "next/server"; import { v2 as cloudinary } from "cloudinary"; import { getSession } from '@auth0/nextjs-auth0'; import prisma from '@/config/PrismaClient'; cloudinary.config({ cloud_name: process.env.CLOUDINARY_CLOUD, api_key: process.env.CLOUDINARY_API_KEY, api_secret: process.env.CLOUDINARY_API_SECRET, }) export const POST = async (request: NextRequest, props: any) => { const { params } = props; const { userId } = params const data = await request.formData(); const image = data.get('posterImage') as File const genre = data.get('genres') as string const review = data.get('review') as string const name = data.get('name') as string const score = data.get('score') as string try { const session = await getSession(); if (!session) { return NextResponse.json('Unauthorized', { status: 401 }) } if (!image) { return NextResponse.json("Image not found", { status: 404 }) } const bytes = await image.arrayBuffer() const buffer = Buffer.from(bytes) if (!bytes || !buffer) { return NextResponse.json("Buffer failed", { status: 404 }) } const upload: any = await new Promise((resolve, reject) => { cloudinary.uploader .upload_stream({ folder: 'movie-uploads', upload_preset: 'movie-uploads' }, (err, result) => { if (err) { console.log(err); reject(err); } console.log('Upload Result:', result); resolve(result); NextResponse.json('Success') }) .end(buffer); }) if (!upload) { return NextResponse.json('Error in upload', { status: 404 }); } const user = await prisma.user.findFirst({ where: { nickname: userId } }) if (!user) { return NextResponse.json('User not found', { status: 404 }); } const genreId = await prisma.genre.findFirst({ where: { name: genre } }) if (!genreId) { return NextResponse.json('Genre not found', { status: 404 }); } const newMovie = await prisma.movie.create({ data: { User: { connect: { id: user.id } }, poster_image: upload.secure_url, poster_image_id: upload.public_id, name: name, score: parseInt(score), critique: review, Genre: { connect: { id: genreId?.id } } } }) if (!newMovie) { return NextResponse.json('Error', { status: 200 }); } return NextResponse.json('Ok', { status: 200 }); } catch (err) { console.log(err); return NextResponse.json('Error in the code', { status: 500 }) } }
错误原因分析
- 提前发送响应破坏请求生命周期:在
cloudinary.upload_stream的回调中调用NextResponse.json('Success'),会提前结束请求并发送响应,导致后续数据库操作等代码无法执行,引发未捕获错误,Vercel返回默认HTML错误页面而非预期的JSON响应。 - Promise处理不严谨:冗余的响应调用导致
upload变量无法正确获取Cloudinary返回结果,最终打印为undefined。 - 状态码逻辑错误:创建电影记录失败时返回200状态码,不符合HTTP规范,易引发前端判断混乱。
修复方案
修复后的完整代码
import { NextResponse, NextRequest } from "next/server"; import { v2 as cloudinary } from "cloudinary"; import { getSession } from '@auth0/nextjs-auth0'; import prisma from '@/config/PrismaClient'; cloudinary.config({ cloud_name: process.env.CLOUDINARY_CLOUD, api_key: process.env.CLOUDINARY_API_KEY, api_secret: process.env.CLOUDINARY_API_SECRET, }); export const POST = async (request: NextRequest, props: any) => { const { params } = props; const { userId } = params; const data = await request.formData(); const image = data.get('posterImage') as File; const genre = data.get('genres') as string; const review = data.get('review') as string; const name = data.get('name') as string; const score = data.get('score') as string; try { const session = await getSession(); if (!session) { return NextResponse.json({ message: 'Unauthorized' }, { status: 401 }); } if (!image) { return NextResponse.json({ message: "Image not found" }, { status: 404 }); } const bytes = await image.arrayBuffer(); const buffer = Buffer.from(bytes); // 更准确的空缓冲区检查 if (!bytes.byteLength || !buffer.length) { return NextResponse.json({ message: "Buffer failed" }, { status: 404 }); } // 移除冗余响应调用,规范Promise处理 const upload = await new Promise((resolve, reject) => { cloudinary.uploader .upload_stream( { folder: 'movie-uploads', upload_preset: 'movie-uploads' }, (err, result) => { if (err) { console.error('Cloudinary Upload Error:', err); return reject(err); } console.log('Upload Result:', result); resolve(result); } ) .end(buffer); }); if (!upload) { return NextResponse.json({ message: 'Error in upload' }, { status: 500 }); } const user = await prisma.user.findFirst({ where: { nickname: userId } }); if (!user) { return NextResponse.json({ message: 'User not found' }, { status: 404 }); } const genreId = await prisma.genre.findFirst({ where: { name: genre } }); if (!genreId) { return NextResponse.json({ message: 'Genre not found' }, { status: 404 }); } const newMovie = await prisma.movie.create({ data: { User: { connect: { id: user.id } }, poster_image: upload.secure_url, poster_image_id: upload.public_id, name: name, score: parseInt(score), critique: review, Genre: { connect: { id: genreId.id } } } }); if (!newMovie) { return NextResponse.json({ message: 'Failed to create movie' }, { status: 500 }); } return NextResponse.json({ message: 'Ok', movie: newMovie }, { status: 200 }); } catch (err) { console.error('Server Error:', err); return NextResponse.json( { message: 'Internal server error', error: err instanceof Error ? err.message : 'Unknown error' }, { status: 500 } ); } };
关键修复点
- 移除
upload_stream回调中的NextResponse.json('Success')调用,确保请求生命周期完整。 - 优化缓冲区空检查逻辑,避免误判。
- 修正错误场景的状态码,符合HTTP规范。
- 增强错误日志输出,便于生产环境排查问题。
- 返回结构化JSON响应,方便前端处理。
验证步骤
- 将修复后的代码替换原API路由文件。
- 在Vercel重新部署项目,确认环境变量同步正常。
- 测试上传功能,检查控制台是否返回正确JSON响应,Cloudinary后台是否能看到上传的图片。
- 若仍有问题,查看Vercel函数日志(
Functions-> 对应API路由),根据详细错误信息进一步排查。
内容的提问来源于stack exchange,提问作者Matias Alaimo Chiappa
相关产品推荐
相关产品推荐

