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 }); } }
核心修改点说明
- 请求对象转换:通过
Readable.fromWeb(req.body)将NextRequest的body转换为Node.js可读流,适配Formidable的解析要求。 - API路由配置:App Router中直接在
config对象设置bodyParser: false,替代旧的api.bodyParser写法。 - 异步解析:使用Formidable v3推荐的
parseAsync()异步方法,避免回调地狱并正确处理Promise。 - 响应规范:用
NextResponse.json()替代错误的res.json(),符合App Router的响应要求。 - 目录处理:用
fs.access()结合catch()更简洁地创建目录,添加recursive: true确保多级目录能被创建。 - 前端优化:给form绑定
onSubmit事件(而非按钮onClick)、添加文件类型限制、禁用未选文件时的提交按钮、完善错误捕获。
内容的提问来源于stack exchange,提问作者venndi
相关产品推荐
相关产品推荐

