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

Heroku文件系统在React+Express项目中图片无法显示问题咨询

为什么纯Express项目在Heroku上能显示上传的图片,React+Express却不行?

嘿,这个问题我碰到过好多次了,咱一点点拆解清楚——其实核心还是Heroku那个**临时文件系统(ephemeral filesystem)**的特性,只是两种项目的部署和访问逻辑不一样,导致表现出来的问题不同。

先搞懂:纯Express项目的“正常”其实是“暂时正常”

你说纯Express项目上传图片后能正常显示,其实只是运气好——因为Heroku的dyno只要不重启(比如没休眠、没部署更新、没缩放),你上传的文件就还存在于当前dyno的文件系统里。这时候你从Express的静态服务请求图片,自然能拿到。但如果等个30分钟(Heroku空闲dyno会休眠),或者重新部署一次,你上传的图片肯定就没了,只是你还没遇到这种情况而已。

那React+Express项目为什么上传后直接看不到?

这就和两种项目的部署、访问逻辑差异有关了,常见的原因有这几个:

1. 前后端部署架构的问题

情况A:前后端分开部署(前端在Vercel/Netlify,后端在Heroku)

这种情况下,你前端上传图片到Heroku后端后,可能犯了一个小错误:前端请求图片用的是相对路径(比如/uploads/xxx.jpg),但这个路径指向的是前端的服务器(比如Vercel的域名),而不是后端的Heroku域名。所以前端其实是在请求前端服务器上的uploads文件夹,那肯定找不到文件啊!但因为前端服务器可能返回了SPA的index.html(200状态码),所以控制台不会报404,只是图片加载失败。

情况B:前后端打包在一起部署(React build后放到Express里)

这种情况常见的坑是静态服务的配置顺序错了。比如你先把React的build文件夹设为静态资源,再配置uploads文件夹的静态服务:

// 错误顺序:先加载React静态资源
app.use(express.static(path.join(__dirname, 'client/build')));
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));

这时候当你请求/uploads/xxx.jpg时,Express会先去client/build文件夹里找,找不到就返回React的index.html(因为SPA的路由 fallback 配置),所以请求返回的是200,但内容是HTML不是图片,自然显示不出来,控制台也不会报错。

2. React SPA的路由特性坑了你

纯Express是多页应用,页面跳转后路径大多是根路径,用相对路径uploads/xxx.jpg没问题。但React是单页应用,如果你的当前路由是/dashboard这种非根路径,用相对路径../uploads/xxx.jpg很容易拼错,导致请求路径不对,同样拿不到图片。

3. 上传路径和静态服务路径不匹配

本地开发时,你用./uploads当上传目录没问题,但部署到Heroku后,Node的工作目录可能和你本地不一样(比如因为打包方式、Heroku的文件结构),导致multer上传的文件实际不在Express静态服务指向的目录里。比如你用__dirname + '/uploads',但Heroku上的__dirname可能是/app,而你静态服务指向的是/app/client/uploads,那文件自然找不到。

怎么临时解决?(最终还是要上云存储)

先给你几个临时能让图片显示的办法,但记住:Heroku的临时文件系统永远不适合存用户上传的文件,最终还是要迁移到AWS S3、Cloudinary这类云存储服务。

  • 如果是前后端分开部署:把图片的请求URL改成后端的完整域名,比如https://your-heroku-app.herokuapp.com/uploads/xxx.jpg,别用相对路径。
  • 如果是一起部署:调整静态服务的顺序,先加载uploads的静态服务,再加载React的静态服务:
    // 正确顺序:先处理uploads的请求
    app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
    // 再处理React的静态资源和路由
    app.use(express.static(path.join(__dirname, 'client/build')));
    // SPA路由fallback
    app.get('*', (req, res) => {
      res.sendFile(path.join(__dirname+'/client/build/index.html'));
    });
    
  • 用绝对路径配置multer的上传目录,还要确保目录存在(因为Heroku重启后目录会消失):
    const fs = require('fs');
    const path = require('path');
    const uploadsDir = path.join(__dirname, 'uploads');
    // 递归创建目录,避免Heroku重启后目录不存在
    fs.mkdirSync(uploadsDir, { recursive: true });
    
    const storage = multer.diskStorage({
      destination: (req, file, cb) => {
        cb(null, uploadsDir);
      },
      filename: (req, file, cb) => {
        cb(null, Date.now() + '-' + file.originalname);
      }
    });
    const upload = multer({ storage: storage });
    
  • React里用绝对路径请求图片,比如/uploads/xxx.jpg,别用相对路径,或者在前端环境变量里配置后端API域名,动态拼接完整URL。

最后再敲个警钟

纯Express项目的“正常”真的只是暂时的,只要dyno一重启,你上传的图片就没了。所以不管哪种项目,只要涉及用户上传的文件,一定要用专门的云存储服务,这才是长久之计。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:54:11