NextJS API同时传文件与表单数据失败:req.body undefined及501错误
NextJS 表单上传文件+表单数据时 req.body 未定义的问题解决
问题概述
开发NextJS应用时,表单需同时收集用户数据和上传文件。使用Multer+next-connect并设置bodyParser: false后,出现501错误:Cannot destructure property 'firstName' of 'req.body' as it is undefined;更换multiparty中间件后问题依旧。调试发现:
- 前端
formData中fileUrl字段为undefined - axios手动设置
Content-Type: multipart/formdata时请求失败
核心问题分析
- 中间件冲突:同时使用multiparty和multer解析
multipart/form-data,导致req.body被覆盖或解析异常 - axios请求配置错误:手动设置
Content-Type会破坏自动生成的boundary,导致后端无法解析表单 - 前端逻辑漏洞:文件选择函数中存在未定义变量,且传递了未初始化的
fileUrl字段 - API路由缺陷:异步操作未用
await、变量未定义、内存存储配置错误
具体修复方案
1. 移除冲突的multiparty中间件
直接删除middleware.js,仅用multer处理文件和表单字段解析——multer本身支持同时解析普通表单数据和文件。
2. 修复前端axios请求配置
axios发送FormData时,无需手动设置Content-Type,它会自动生成带正确boundary的请求头;同时移除不必要的fileUrl字段传递(该字段由后端生成)。
修改useSubmitCV.js:
import { useState } from 'react'; import { toast } from 'react-toastify'; import axios from 'axios'; import { useRouter } from 'next/router'; export const useSubmitCV = () => { const [error, setError] = useState(null); const [loading, setLoading] = useState(null); const router = useRouter(); const API_URL = '/api/cvs/addcv'; const submitcv = async (formData) => { setLoading(true); setError(null); const form = new FormData(); // 仅提交前端已有的表单数据,移除未初始化的fileUrl form.append('firstName', formData.firstName); form.append('lastName', formData.lastName); form.append('email', formData.email); form.append('phone', formData.phone); form.append('cvfile', formData.cvfile); form.append('address', formData.address); form.append('city', formData.city); form.append('state', formData.state); form.append('zip', formData.zip); form.append('usEligible', formData.usEligible); form.append('dob', formData.dob); form.append('description', formData.description); // 不手动设置Content-Type,axios自动处理 const response = await axios.post(API_URL, form); if (response.data.message) { setError(response.data.message); toast.error(response.data.message); setLoading(false); return; } toast.success('Resume Successfully Submitted'); setLoading(false); router.push('/'); }; return { submitcv, loading, error }; };
3. 修复前端文件选择逻辑
修正onFileChange中未定义变量的问题:
const onFileChange = (e) => { if (!e.target.files[0]) { setFormData((prevState) => ({ ...prevState, cvfile: null, })); } else { setFormData((prevState) => ({ ...prevState, cvfile: e.target.files[0], })); } };
4. 重构API路由(addcv.js)
移除多余中间件、修复异步操作、修正内存存储配置:
import nextConnect from 'next-connect'; import multer from 'multer'; import { v4 as uuidv4 } from 'uuid'; import { File, Web3Storage } from 'web3.storage'; import CV from '../../../models/cvModel'; const w3storage = new Web3Storage({ token: process.env.WEB3_STORAGE_TOKEN }); function checkFileType(file, cb) { const filetypes = /doc|docx|pdf|odf/; const extname = filetypes.test(path.extname(file.originalname).toLowerCase()); const mimetype = filetypes.test(file.mimetype); if (mimetype && extname) { return cb(null, true); } else { cb('Error: Documents Only!'); } } // 内存存储无需配置destination,文件直接存在内存中 const storage = multer.memoryStorage({ filename: (req, file, cb) => { cb(null, uuidv4() + '-' + file.originalname); }, fileFilter: function (_req, file, cb) { checkFileType(file, cb); }, }); const maxSize = 3 * 1000 * 1000; const upload = multer({ storage: storage, limits: { fileSize: maxSize } }); const createCV = nextConnect({ onError(error, req, res) { res.status(501).json({ error: `Sorry something Happened! ${error.message}` }); }, onNoMatch(req, res) { res.status(405).json({ error: `Method '${req.method}' Not Allowed` }); }, }) .use(upload.single('cvfile')) .post(async (req, res) => { // multer解析后,req.body为普通表单字段,req.file为上传文件 const { firstName, lastName, email, phone, address, city, state, zip, usEligible, dob, description } = req.body; if (!req.file) { return res.status(400).json({ error: 'No file uploaded' }); } const fileName = req.file.originalname; const newFile = new File([req.file.buffer], fileName); // 等待web3.storage上传完成 const cid = await w3storage.put([newFile]); // 等待数据库创建完成 const cv = await CV.create({ firstName, lastName, email, phone, cvfile: fileName, address, city, state, zip, usEligible, dob, description, fileUrl: `https://${cid}.ipfs.dweb.link/${fileName}`, }); res.status(201).json(cv); }); export default createCV; export const config = { api: { bodyParser: false, }, };
5. 确保表单绑定提交事件
在CVForm.jsx的form标签上添加onSubmit:
<form onSubmit={cvSubmit}> {/* 现有表单内容 */} </form>
内容的提问来源于stack exchange,提问作者Erik James Robles
相关产品推荐
相关产品推荐

