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

NextJS13中使用formidable无法上传文件至public文件夹问题

解决NextJS 13 + Formidable v3 图片上传报错TypeError: req.on is not a function

错误原因

NextJS 13 App Router的API路由中,req是NextRequest实例(并非传统Node.js的IncomingMessage),而Formidable v3默认期望接收Node.js原生请求对象,直接传递req会导致req.on方法不存在的错误。此外原代码还有几处适配App Router的问题:API路由配置写法错误、响应方式不符合规范、Formidable解析未正确处理异步逻辑。

修正后的代码

前端上传页面(优化版)

'use client';
import { useState } from 'react';
import axios from 'axios';

export default function Home() {
  const [selectedFile, setSelectedFile] = useState(null);
  const [selectedImage, setSelectedImage] = useState('');

  const submitHandler = async (e) => {
    e.preventDefault();
    if (!selectedFile) return;
    
    const formData = new FormData();
    formData.append('image', selectedFile);
    
    try {
      const res = await axios.post('/api', formData, {
        headers: { 'Content-Type': 'multipart/form-data' }
      });
      console.log(res.data);
    } catch (err) {
      console.error('上传失败:', err);
    }
  };

  return (
    <div className='p-8'>
      <form onSubmit={submitHandler}>
        <label>
          <input
            type='file'
            accept='image/*'
            hidden
            onChange={({ target }) => {
              if (target.files?.[0]) {
                const file = target.files[0];
                setSelectedImage(URL.createObjectURL(file));
                setSelectedFile(file);
              }
            }}
          />
          <div className='w-40 aspect-video rounded flex items-center justify-center border-2 border-dashed cursor-pointer bg-gray-50'>
            {selectedImage ? (
              <img src={selectedImage} alt="预览" className="w-full h-full object-cover rounded" />
            ) : (
              <span className='text-gray-500'>选择图片</span>
            )}
          </div>
        </label>
        <button 
          type='submit' 
          disabled={!selectedFile}
          className='mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:bg-gray-300'
        >
          提交上传
        </button>
      </form>
    </div>
  );
}

后端API路由(关键修正)

import { NextResponse } from 'next/server';
import formidable from 'formidable';
import path from 'path';
import fs from 'fs/promises';
import { Readable } from 'stream';

// App Router中禁用bodyParser的正确方式
export const config = {
  bodyParser: false,
};

const readFile = async (req, saveLocally) => {
  const options = {
    maxFileSize: 4 * 1024 * 1024, // 4GB限制,写法更清晰
    ...(saveLocally && {
      uploadDir: path.join(process.cwd(), 'public', 'images'),
      filename: (name, ext, file) => {
        return `${Date.now()}_${file.originalFilename}`;
      },
    }),
  };

  const form = formidable(options);
  // 将NextRequest转换为Node可读流,适配Formidable
  const readableStream = Readable.fromWeb(req.body);
  // 使用Formidable v3推荐的异步解析方法
  return await form.parseAsync(readableStream);
};

export async function POST(req) {
  try {
    // 确保images目录存在,递归创建多级目录
    const imagesDir = path.join(process.cwd(), 'public', 'images');
    await fs.access(imagesDir).catch(() => fs.mkdir(imagesDir, { recursive: true }));

    // 解析表单数据
    const { fields, files } = await readFile(req, true);
    
    return NextResponse.json({ 
      done: 'ok', 
      files: files 
    });
  } catch (error) {
    console.error('上传处理失败:', error);
    return NextResponse.json({ error: '上传失败' }, { status: 500 });
  }
}

核心修改点说明

  1. 请求对象转换:通过Readable.fromWeb(req.body)将NextRequest的body转换为Node.js可读流,适配Formidable的解析要求。
  2. API路由配置:App Router中直接在config对象设置bodyParser: false,替代旧的api.bodyParser写法。
  3. 异步解析:使用Formidable v3推荐的parseAsync()异步方法,避免回调地狱并正确处理Promise。
  4. 响应规范:用NextResponse.json()替代错误的res.json(),符合App Router的响应要求。
  5. 目录处理:用fs.access()结合catch()更简洁地创建目录,添加recursive: true确保多级目录能被创建。
  6. 前端优化:给form绑定onSubmit事件(而非按钮onClick)、添加文件类型限制、禁用未选文件时的提交按钮、完善错误捕获。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 16:37:44