Next.js API上传图片至S3失败:请求无响应问题排查
多文件上传至S3存储桶的问题修复
问题现象
上传图片到S3时失败,收到提示:
API resolved without sending a response for /api/upload/uploadPhoto, this may result in stalled requests.
前端通过多选文件框收集图片存入listingPhotos数组,提交时用FormData发送POST请求到Next.js API,但后端打印req.body得到:
req.body ------WebKitFormBoundarydsKofVokaJRIbco1 Content-Disposition: form-data; name="files[]" [object File][object File][object File][object File] ------WebKitFormBoundarydsKofVokaJRIbco1--
无法正确解析文件,导致S3上传失败。
错误原因分析
前端问题
- FormData添加文件方式错误:直接将数组
listingPhotosappend到files[],导致文件被转成字符串[object File],而非正确的文件对象。 - 手动设置Content-Type:手动指定
multipart/form-data会丢失浏览器自动生成的boundary,导致后端无法正确分割表单数据。
后端问题
- Next.js默认不解析multipart/form-data:原生
req.body无法获取解析后的文件内容,手动分割body的方式不可靠且容易出错。 - 响应方式错误:用
return { statusCode: ... }不符合Next.js API路由的响应规范,应该使用res对象的方法发送响应。 - S3参数错误:
ContentType设置为对象{ "image/png": "image/jpg" }完全错误,应该使用文件实际的MIME类型;文件名提取逻辑错误,无法正确获取文件名。
修正后的代码
前端代码
图片选择处理函数(无改动)
const handleListingPhotos = async (e: any) => { setMessage(null); let file = e.target.files; console.log("hello", file); for (let i = 0; i < file.length; i++) { const fileType = file[i]["type"]; const validImageTypes = ["image/jpeg", "image/png"]; if (validImageTypes.includes(fileType)) { setListingPhotos((prev: any) => { return [...prev, file[i]]; }); } else { setMessage("Only images are accepted"); } } };
提交请求函数(修正后)
const handleSubmit = async (e: any) => { e.preventDefault(); const formData = new FormData(); // 循环添加每个文件到FormData listingPhotos.forEach(file => { formData.append("files[]", file); }); try { const res = await fetch(`/api/upload/uploadPhoto`, { method: "POST", // 移除手动设置的Content-Type,浏览器会自动处理 body: formData, }); const data = await res.json(); console.log("上传结果:", data); } catch (err) { console.error("上传失败:", err); } };
后端API代码(使用formidable解析表单)
首先安装依赖:
npm install formidable # 或 yarn add formidable
然后修改api/upload/uploadPhoto.ts:
import { NextApiRequest, NextApiResponse } from "next"; const AWS = require("aws-sdk"); import formidable from "formidable"; // 禁用Next.js默认的body解析 export const config = { api: { bodyParser: false, }, }; const access = { accessKeyId: process.env.AWS_ACCESS_KEY_ID as string, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string, }; const s3 = new AWS.S3({ region: "your-region", credentials: access }); export default async function uploadPhoto( req: NextApiRequest, res: NextApiResponse ) { if (req.method !== "POST") { return res.status(405).json({ message: "Method Not Allowed" }); } const form = formidable({ multiples: true }); try { const { files } = await new Promise((resolve, reject) => { form.parse(req, (err, fields, files) => { if (err) reject(err); resolve({ fields, files }); }); }); // 统一处理单个/多个文件的情况 const uploadFiles = Array.isArray(files["files[]"]) ? files["files[]"] : [files["files[]"]]; // 批量上传到S3 const uploadPromises = uploadFiles.map(async (file: any) => { const params = { Bucket: "your-bucket-name", Key: file.originalFilename, Body: require("fs").createReadStream(file.filepath), ContentType: file.mimetype, }; await s3.putObject(params).promise(); return `Successfully uploaded: ${params.Bucket}/${params.Key}`; }); const results = await Promise.all(uploadPromises); res.status(200).json({ message: "All files uploaded", results }); } catch (err) { console.error("上传错误:", err); res.status(500).json({ message: "Upload failed", error: err }); } }
关键说明
- 前端:必须循环添加每个文件到FormData,不要手动设置Content-Type,让浏览器自动处理boundary。
- 后端:禁用Next.js默认的bodyParser,使用
formidable解析multipart/form-data,它会自动处理文件上传的细节,包括文件路径、MIME类型等。 - S3上传:使用文件流
createReadStream上传,比读取整个文件更高效;正确设置ContentType为文件的实际MIME类型。
内容的提问来源于stack exchange,提问作者jhoangqm
相关产品推荐
相关产品推荐

