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

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;

关键修复说明

  1. 后端配置修正:将Api改为小写api,确保Next.js禁用默认bodyParser,让Formidable能正常处理文件上传请求
  2. Formidable异步处理:用Promise包装parse方法,保证表单解析完成后再返回响应,避免请求挂起
  3. 前端FormData优化:移除第三方依赖,使用浏览器原生API,同时确保表单字段始终被提交,避免空请求导致的异常
  4. 类型安全优化:修正TypeScript类型忽略,让代码更规范

内容的提问来源于stack exchange,提问作者Abdallah Safieddine

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 17:56:09