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

Nextjs API转发FormData至Express后端报405错误(本地正常线上失效)

问题:Next.js API转发音频到Express后端生产环境报405错误

已解决 / 详情见本人回答

用户在Next.js前端录制的音频可正常发送到Next.js API,但将该音频从Next.js API转发到Vercel上的Express.js后端时,出现405错误请求。

重要说明

  • 本地环境一切正常,但部署到Vercel生产环境后失效(客户端和服务端均为Vercel上的Node应用,一个是旧版Next.js,另一个是Express)
  • 出于安全考虑,使用Next.js作为中间层API

问题推测

目前推测是formData.append工作异常,因为无论如何打印FormData或其条目,得到的都是空对象{}。

相关代码片段:

const formDataToSend = new FormData()

formDataToSend.append(fileName, new Blob([readResult], { type: 'audio/wav' }), `${fileName}.wav`)

版本信息

  • Next.js版本:13.5.4
  • Express.js版本:4.18.2

Express.js后端代码

app.use(cors()) 

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, '/tmp/') 
  },
  filename: function (req, file, cb) {
    cb(null, `${file.originalname}`)
  }
})

const upload = multer({ storage: storage })

// ...

app.post('/upload', [requireSession, upload.any()], async (req, res) =>
{
  // 请求根本无法到达这里
  // requireSession工作正常
})

Next.js中间层API代码

import { csrf } from '../../../../lib/csrf'
import axios from 'axios'
import crypto from 'crypto'
import multiparty from 'multiparty'
import fs from 'fs'

// 有效URL
const ENDPOINT_URL = 'https://my-api.com/upload'

// 工作正常
async function readAudioFile(filePath) {
  return new Promise((resolve, reject) => {
    const readStream = fs.createReadStream(filePath)
    const chunks = []
    readStream.on('data', (chunk) => {
      chunks.push(chunk)
    })
    readStream.on('end', () => {
      const fileBuffer = Buffer.concat(chunks)
      resolve(fileBuffer)
    })
    readStream.on('error', (err) => {
      reject(err)
    })
  })
}

async function post(req, res)
{
  try {
    // 工作正常
    const form = new multiparty.Form()

    // 工作正常
    const formData = await new Promise((resolve, reject) => {
      form.parse(req, function (err, fields, files) {
        if (err) reject({ err })
        resolve({ fields, files })
      })
    })

    // 工作正常
    const readResult = await readAudioFile(formData.files.audio[0].path)

    // 工作正常
    const randomizer = `${req?.headers?._uuid}-${req?.query?.taskId}-${(new Date().getTime()).toString(16)}`

    // 工作正常
    const fileName = crypto.createHash('sha256').update(randomizer).digest('hex')

    // 工作正常
    const formDataToSend = new FormData()

    // !! 不确定。打印时,"formDataToSend"和"formDataToSend.entries()"均为空对象{}
    formDataToSend.append(fileName, new Blob([readResult], { type: 'audio/wav' }), `${fileName}.wav`)

    // 此处无未定义项,工作正常
    const headers = { 
      'client': process.env.API_ACCESS_KEY, 
      '_session': req.headers._session,
      '_uuid': req.headers._uuid,
      ...req.headers,
      'Content-Type': 'multipart/form-data', 
      'Request-Index': '0' ,
    }
  
    // 此请求失败,返回405错误
    await axios.post(ENDPOINT_URL, formDataToSend, { headers, params: { ...req.query } })
    .then(response => (res.status(200).json(response.data)))
    .catch(error => (console.log(error), res.status(500).json({
      ...getErrorMessage(),
      errorDetails: error,
      dataToSend
    })))
  }
  catch (error) {
    console.log(error)
    res.status(501).json({
      ...getErrorMessage(),
      errorDetails: error,
      dataToSend
    })
  }
}

async function handler(req, res) 
{
  switch (req.method.toLowerCase())
  {
    case 'post':
      await post(req, res)
      break
    default:
      res.status(200).send(`Method ${req.method} is not allowed.`)
      break
  }
}

const getErrorMessage = () => { return { time: Date.now(), result: false, status: 500, message: 'Something went wrong' } }

export default csrf(handler)

export const config = {
  api: {
    bodyParser: false
  }
}

已尝试的方案

  • 用fetch替代axios发送请求
  • 调整Express.js端点的CORS设置

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 20:46:06