You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 })
    }
}

错误原因分析

  1. 提前发送响应破坏请求生命周期:在cloudinary.upload_stream的回调中调用NextResponse.json('Success'),会提前结束请求并发送响应,导致后续数据库操作等代码无法执行,引发未捕获错误,Vercel返回默认HTML错误页面而非预期的JSON响应。
  2. Promise处理不严谨:冗余的响应调用导致upload变量无法正确获取Cloudinary返回结果,最终打印为undefined。
  3. 状态码逻辑错误:创建电影记录失败时返回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响应,方便前端处理。

验证步骤

  1. 将修复后的代码替换原API路由文件。
  2. 在Vercel重新部署项目,确认环境变量同步正常。
  3. 测试上传功能,检查控制台是否返回正确JSON响应,Cloudinary后台是否能看到上传的图片。
  4. 若仍有问题,查看Vercel函数日志(Functions -> 对应API路由),根据详细错误信息进一步排查。

内容的提问来源于stack exchange,提问作者Matias Alaimo Chiappa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 02:52:25