Multer diskStorage在Vercel生产环境出现文件目录不存在错误
Vercel部署后Multer文件上传报错ENOENT
前端基于React开发,后端采用Node.js+Express+Multer+MongoDB搭建,本地开发环境(localhost)运行完全正常,Postman测试无异常,但部署至Vercel生产环境后,Axios PUT请求返回错误:ENOENT: no such file or directory, open 'public/startupIcons/startupIcon-1673682105301-871934982.jpg',确认该错误由Multer配置问题导致,现有Multer配置代码如下:
const storage = multer.diskStorage({ destination: (req, file, cb) => { if (file.fieldname === 'startupIcon') { cb(null, '/public/images'); } else if (file.fieldname === 'companyId') { cb(null, '/uploads/companyIds'); } else if (file.fieldname === 'documents') { cb(null, '/uploads/companyIds'); } }, filename: (req, file, cb) => { cb(null, `${new Date().toISOString().replace(/:/g, '-')}-${file.originalname}`); }, }); const filter = (req, file, cb) => { const type = file.mimetype.split('/')[1]; if (file.fieldname === 'startupIcon') { if (type === 'jpg' || type === 'jpeg' || type === 'png') { cb(null, true); } else { cb(new Error('Not a jpg/jpeg File!!'), false); } } if (file.fieldname === 'companyId' || file.fieldname === 'documents') { if (type === 'pdf' || type === 'jpg' || type === 'jpeg' || type === 'png') { cb(null, true); } else { cb(new Error('file should be in .pdf|.jpg|.jpeg|.png format!!'), false); } } }; const upload = multer({ storage, limits: { fileSize: 1000000, // fieldSize: 1016 * 1016, }, fileFilter: filter, }); module.exports = upload;
核心原因
- Vercel无服务器架构限制:Vercel的Serverless Functions是临时运行环境,本地磁盘存储不可靠,且系统根目录(
/)下不存在public/images这类目录,Vercel的文件系统除临时目录外基本只读,重启后文件会丢失。 - 绝对路径错误:代码中使用的
/public/images是系统根目录绝对路径,本地开发环境可能有效,但Vercel部署环境的应用根目录并非系统根目录,导致找不到对应文件夹。 - 目录未提前创建:即使路径正确,Vercel环境不会自动创建不存在的目录,Multer尝试写入时直接报错。
解决方案
1. 改用云存储(生产环境推荐)
Vercel不适合本地文件存储,建议将上传文件存储到云服务(如Cloudinary、AWS S3、阿里云OSS等),以Cloudinary为例修改Multer配置:
const cloudinary = require('cloudinary').v2; const { CloudinaryStorage } = require('multer-storage-cloudinary'); const multer = require('multer'); // 配置Cloudinary cloudinary.config({ cloud_name: '你的Cloudinary云名称', api_key: '你的API密钥', api_secret: '你的API密钥' }); // 配置云存储引擎 const storage = new CloudinaryStorage({ cloudinary: cloudinary, params: { folder: (req, file) => { return file.fieldname === 'startupIcon' ? 'public/images' : 'uploads/companyIds'; }, format: (req, file) => file.mimetype.split('/')[1], public_id: (req, file) => `${new Date().toISOString().replace(/:/g, '-')}-${file.originalname}` } }); // 保留原文件过滤逻辑 const filter = (req, file, cb) => { const type = file.mimetype.split('/')[1]; if (file.fieldname === 'startupIcon') { if (['jpg', 'jpeg', 'png'].includes(type)) { cb(null, true); } else { cb(new Error('仅支持jpg/jpeg/png格式文件!!'), false); } } else if (['companyId', 'documents'].includes(file.fieldname)) { if (['pdf', 'jpg', 'jpeg', 'png'].includes(type)) { cb(null, true); } else { cb(new Error('仅支持pdf/jpg/jpeg/png格式文件!!'), false); } } }; const upload = multer({ storage, limits: { fileSize: 1000000 }, fileFilter: filter }); module.exports = upload;
2. 临时测试方案:使用Vercel临时目录
如果仅用于测试,可使用Vercel提供的/tmp临时目录,修改Multer的destination配置:
const multer = require('multer'); const path = require('path'); const fs = require('fs'); const storage = multer.diskStorage({ destination: (req, file, cb) => { let destPath; if (file.fieldname === 'startupIcon') { destPath = path.join('/tmp', 'public/images'); } else { destPath = path.join('/tmp', 'uploads/companyIds'); } // 递归创建目录 fs.mkdirSync(destPath, { recursive: true }); cb(null, destPath); }, filename: (req, file, cb) => { cb(null, `${new Date().toISOString().replace(/:/g, '-')}-${file.originalname}`); } }); // 保留原filter和upload配置 const filter = (req, file, cb) => { const type = file.mimetype.split('/')[1]; if (file.fieldname === 'startupIcon') { if (type === 'jpg' || type === 'jpeg' || type === 'png') { cb(null, true); } else { cb(new Error('Not a jpg/jpeg File!!'), false); } } if (file.fieldname === 'companyId' || file.fieldname === 'documents') { if (type === 'pdf' || type === 'jpg' || type === 'jpeg' || type === 'png') { cb(null, true); } else { cb(new Error('file should be in .pdf|.jpg|.jpeg|.png format!!'), false); } } }; const upload = multer({ storage, limits: { fileSize: 1000000 }, fileFilter: filter }); module.exports = upload;
注意:临时目录的文件在函数执行结束后会被清除,无法持久化存储,不适合生产环境。
3. 本地开发路径修正(仅本地有效)
如果仅用于本地开发,将绝对路径改为相对路径:
const multer = require('multer'); const path = require('path'); const storage = multer.diskStorage({ destination: (req, file, cb) => { let destPath; if (file.fieldname === 'startupIcon') { // 根据你的项目目录结构调整相对路径 destPath = path.join(__dirname, '../public/images'); } else { destPath = path.join(__dirname, '../uploads/companyIds'); } cb(null, destPath); }, filename: (req, file, cb) => { cb(null, `${new Date().toISOString().replace(/:/g, '-')}-${file.originalname}`); } }); // 保留原filter和upload配置 // ...
内容的提问来源于stack exchange,提问作者MD. AMINUL ISLAM
相关产品推荐
相关产品推荐

