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

Heroku部署API请求出现/undefined/api前缀异常求助

排查Heroku部署后请求路径出现/undefined/api/clinics的问题

问题背景

部署应用到Heroku后,日志中发现异常请求路径:/undefined/api/clinics。代码中未在api前缀前定义任何变量,Heroku配置仅包含ACCESS_TOKEN_SECRET、MONGODB_URI和REFRESH_TOKEN_SECRET,数据库连接正常(用户可完成登录、登出操作)。直接访问/api/clinics可正常获取诊所数据。

前端代码(获取诊所数据)

useEffect(() => {
  const fetchClinics = async () => {
    try {
      const response = await axios.get('https://dental-w-032fe80aafac.herokuapp.com/api/clinics');
      setClinics(response.data)
  
    } catch (error) {
      console.error(error)
    }
  }
  fetchClinics()
}, [])

后端相关代码

index.js

app.use('/api', require('./routes/authRouter'));

const client = new MongoClient(process.env.MONGODB_URI);

app.use(express.static(buildPath));

// create storage engine
const storage = new GridFsStorage({
  url: process.env.MONGODB_URI,
  file: (req, file) => {
    return {
      filename: `${Date.now()}${path.extname(file.originalname)}`
    };
  }
});

const upload = multer({ storage });

app.get('/', (req, res) => {
    res.json({ message: 'Welcome to the server' });
});

// Multer Middleware for file upload
app.post('/api/clinics', upload.single('image'), (req, res) => {
  const rating = Number((Math.random() * (5 - 3.5) + 3.5).toFixed(1))
  let newClinic = new Clinic({
      name: req.body.name, 
      address: req.body.address,
      doctor: req.body.doctor,    
      price: req.body.price, 
      rating,
      image: { // store file data
          id: req.file.id,
          contentType: req.file.contentType,
          filename: req.file.filename
      }
  });

  newClinic.save()
      .then(clinic => res.json(clinic))
      .catch(err => res.status(400).json('Error:' + err));
});

// Route for serving images
app.get('/api/image/:filename', async (req, res) => {

  const { filename } = req.params;
  let image = imageCache.get(filename);

  // Load from the cache if possible
  if (image) {
    res.setHeader('Content-Type', image.contentType); 
    res.send(image.buffer);
    return;
  }

  try {
      await client.connect();
      const db = client.db("test"); 
      const bucket = new GridFSBucket(db, {
          bucketName: "fs" 
      });

      const downloadStream = bucket.openDownloadStreamByName(filename);
      let chunks = [];
      downloadStream.on('data', (chunk) => {
        chunks.push(chunk);
      });

      downloadStream.on('end', () => {
        let buffer = Buffer.concat(chunks);
        let imageObject = {
          contentType: 'image/jpeg',
          buffer
        };
        // Cache the image
        imageCache.set(filename, imageObject);
        res.setHeader('Content-Type', imageObject.contentType); 
        res.send(buffer);
      });

  } catch (err) {
      console.error(err);
      res.status(404).send('Image not found');
  }
});

routes/authRouter.js

// routes/authRouter.js
router.get('/clinics', authCtrl.getClinics);
router.post('/clinics', authCtrl.createClinics);

authCtrl.js

const Clinic = require('../models/clinicsModel')
const authCtrl = {
  getClinics : async (req, res) => {
    try {
      const clinics = await Clinic.find().sort({ createdAt: -1 });
      res.json(clinics);
    } catch (error) {
      console.error(error);
      res.status(500).send('An error occurred while fetching the clinics.');
    }
  },

  createClinics : async (req, res) => {
    try {
      const {name, address, medicalWorker} = req.body;
    
      if (!name || !address || !medicalWorker) {  
        return res.status(400).json({message: 'Bad request: name, medicalWorker, and address are required'});
      }
    
      const image = req.file ? req.file.filename : null;
      const newClinic = new Clinic({name, medicalWorker, address, image});
      const savedClinic = await newClinic.save();
    
      res.json(savedClinic);
    } catch (error) {
      console.error(error);
      res.status(500).json({message: 'Server error. Please try again later.'});
    }
  },
}

已尝试的解决方案

  • 将请求URL改为相对路径
  • 调整CORS设置
  • 配置前端proxy:
{
  "name": "client",
  "version": "0.1.0",
  "proxy": "https://dental-w-032fe80aafac.herokuapp.com",
  "private": true,
  ...
}

排查思路与解决建议

  1. 检查环境变量配置

    • 确认前端是否使用了环境变量拼接API URL,比如${process.env.REACT_APP_API_URL}/api/clinics。如果REACT_APP_API_URL未在Heroku的前端环境变量中配置,会导致拼接出undefined/api/clinics。
    • 若使用Create React App,需确保环境变量以REACT_APP_开头,并在Heroku的应用设置中添加对应变量。
  2. 检查axios全局配置

    • 查看是否在前端代码中设置了axios的baseURL,比如:
      axios.defaults.baseURL = process.env.REACT_APP_API_BASE;
      
      如果该环境变量未定义,所有请求都会带上undefined前缀。
  3. 检查请求拦截器

    • 确认是否存在axios请求拦截器,是否在拦截器中错误修改了请求URL,比如拼接了未赋值的变量。
  4. 查看浏览器Network日志

    • 在浏览器开发者工具的Network标签中,找到发送/undefined/api/clinics的请求,查看请求的发起源(Initiator),确认是哪个组件或脚本发送的请求,定位代码问题。
  5. 检查打包后的代码

    • 查看前端打包后的静态文件,搜索undefined/api/clinics,确认URL是如何被错误生成的。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 05:42:11