Next.js 13中Formidable解析函数无响应,API持续pending
解决Next.js API处理表单上传时Pending无响应的问题
核心问题排查
你的API一直处于pending状态,主要是两个关键错误导致:
- Next.js的API配置字段写错,默认bodyParser未被禁用,Formidable无法正确解析multipart/form-data请求
- Formidable的
parse是回调式API,直接用await不会等待解析完成,请求体未被消费导致请求挂起
修复步骤
1. 修正后端API代码
import type { NextApiRequest, NextApiResponse } from "next"; import { IncomingForm } from "formidable"; export default async function handle(request: NextApiRequest, response: NextApiResponse) { try { // 用Promise包装parse方法,确保解析完成后再返回响应 await new Promise((resolve, reject) => { new IncomingForm().parse(request, (err, fields, files) => { if (err) { reject(err); return; } // 这里可添加文件保存、数据库更新等业务逻辑 console.log("解析到的字段:", fields); console.log("解析到的文件:", files); resolve({ fields, files }); }); }); response.status(200).json({ message: "更新成功" }); } catch (e) { console.error("处理错误:", e); response.status(500).json({ message: "更新失败" }); } } // 注意字段是小写的api,Next.js配置要求严格区分大小写 export const config = { api: { bodyParser: false, }, };
2. 修正前端代码逻辑
import React, { FormEvent, useRef, useState } from "react"; import { useSession } from "next-auth/react"; import Paper from "@mui/material/Paper"; import { Avatar, Box, Button, Grid, TextField, Typography } from "@mui/material"; // 移除第三方form-data依赖,直接用浏览器原生FormData // import FormData from "form-data"; const Profile = () => { const { data: session } = useSession(); const filePathRef = useRef<HTMLInputElement | null>(null); const initialState = { firstName: session?.user.firstname ?? "", lastName: session?.user.lastname ?? "", currentPassword: "", profilePicUrl: session?.user.profileImage ?? "" }; const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0]; if (file) { const uri = URL.createObjectURL(file); setFormData(prev => ({ ...prev, profilePicUrl: uri })); setFile(file); } else { setFormData(prev => ({ ...prev, profilePicUrl: "" })); setFile(null); } }; const [formData, setFormData] = useState(initialState); const [file, setFile] = useState<File | null>(null); const [state, setState] = useState({ isError: false, message: "" }); const handleFormChange = (event: React.ChangeEvent<HTMLInputElement>) => { const { name, value } = event.target; setFormData(prev => ({ ...prev, [name]: value })); }; function isFormChanged() { return formData.firstName !== initialState.firstName || formData.lastName !== initialState.lastName || (file !== null && formData.profilePicUrl !== initialState.profilePicUrl); } const handleSubmit = async (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); if (!isFormChanged()) return; const data = new FormData(); // 无论是否有文件,都要提交表单字段,避免空请求 data.append("firstName", formData.firstName); data.append("lastName", formData.lastName); data.append("password", formData.currentPassword); if (file) { data.append("file", file); } try { const response = await fetch('/api/admin', { method: 'POST', body: data }); if (response.ok) { setState(prev => ({ ...prev, isError: false, message: '更新成功!' })); } else { setState(prev => ({ ...prev, isError: true, message: '更新失败,当前密码错误' })); } } catch (err) { console.error("请求错误:", err); setState(prev => ({ ...prev, isError: true, message: '请求失败,请重试' })); } }; return ( <> <h1>个人资料</h1> <Box> <Typography variant="h4" sx={{ paddingBottom: 4 }}> 你好 {session ? `${session.user.firstname} ${session.user.lastname}` : "用户"}, 欢迎来到你的个人资料页 👋 </Typography> <Paper sx={{ padding: "1rem 2rem" }}> <Grid container justifyContent="center"> <Grid item xs={12} sm={8} md={6}> <Box display="flex" flexDirection="column" alignItems="center"> <Avatar sx={{ height: 100, width: 100, marginBottom: 2 }} onClick={() => filePathRef.current?.click()} src={formData.profilePicUrl} /> <Typography variant="h6" sx={{ paddingBottom: 4, color: state.isError ? 'red' : 'green' }}> {state.message} </Typography> </Box> <form method="post" onSubmit={handleSubmit} style={{ maxWidth: 600, margin: "0 auto" }}> <Grid container spacing={3}> <Grid item xs={12} sm={6}> <TextField required fullWidth label="名字" name="firstName" value={formData.firstName} onChange={handleFormChange} /> </Grid> <Grid item xs={12} sm={6} hidden> <input name="image" type="file" id="image" ref={filePathRef} onChange={handleImageChange} /> </Grid> <Grid item xs={12} sm={6}> <TextField required fullWidth label="姓氏" name="lastName" value={formData.lastName} onChange={handleFormChange} /> </Grid> <Grid item xs={12}> <TextField required fullWidth type="password" label="当前密码" name="currentPassword" value={formData.currentPassword} onChange={handleFormChange} /> </Grid> <Grid item xs={12}> <Button type="submit" variant="contained" color="primary"> 保存修改 </Button> </Grid> </Grid> </form> </Grid> </Grid> </Paper> </Box> </> ); }; export default Profile;
关键修复说明
- 后端配置修正:将
Api改为小写api,确保Next.js禁用默认bodyParser,让Formidable能正常处理文件上传请求 - Formidable异步处理:用Promise包装
parse方法,保证表单解析完成后再返回响应,避免请求挂起 - 前端FormData优化:移除第三方依赖,使用浏览器原生API,同时确保表单字段始终被提交,避免空请求导致的异常
- 类型安全优化:修正TypeScript类型忽略,让代码更规范
内容的提问来源于stack exchange,提问作者Abdallah Safieddine
相关产品推荐
相关产品推荐

