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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 01:45:34