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

Next.js如何在API路由中获取上传图片并实现存储

前端代码问题修正

你当前写的前端上传代码有2个会直接导致上传失败的问题:

  • FormData传参错误:fd.append('myfile',image.name) 只传了图片的文件名字符串,没有传入真实的文件对象,后端拿不到文件内容。这里第二个参数应该传入你从文件选择器拿到的完整File对象,而不是它的name属性。
  • 请求头配置错误:手动设置"Content-Type": "image/jpeg" 会导致FormData上传失败。传输FormData时浏览器会自动生成带boundary分隔标识的Content-Type请求头,手动设置会覆盖这个值,导致后端无法正确解析表单内容,直接删掉这个headers配置即可。

修正后的前端代码参考:

const onSubmit = async(e) => { 
  e.preventDefault();
  const fd = new FormData()
  // 假设image是你从<input type="file">拿到的文件对象
  fd.append('myfile', image)
  let res = await fetch(`http://localhost:3000/api/upload`,{
    method: 'POST',
    // 不要手动设置Content-Type,浏览器自动处理
    body: fd,
  })
  let response = await res.json(); 
}
Next.js API 路由接收文件、保存到public文件夹实现

注意:这个方案仅适用于本地开发调试,生产环境禁止使用,因为Next.js构建部署后public文件夹是只读的,运行时写入的文件不会被对外访问,也会在实例重启/重新部署时丢失

分两种常用路由模式提供实现代码:

Pages Router 实现(pages/api/upload.js)

Next.js 原生支持FormData解析,不需要额外安装第三方解析库,配合node原生fs、path模块即可写入文件:

import fs from 'fs'
import path from 'path'

export const config = {
  api: {
    bodyParser: false, // 关闭默认body解析,兼容全版本formdata处理
  },
}

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ message: '方法不允许' })
  }

  try {
    const formData = await req.formData()
    const file = formData.get('myfile')
    if (!file) {
      return res.status(400).json({ message: '未接收到上传文件' })
    }

    // 校验文件类型,只允许图片
    if (!file.type.startsWith('image/')) {
      return res.status(400).json({ message: '仅支持上传图片文件' })
    }

    // 读取文件二进制内容
    const fileBuffer = Buffer.from(await file.arrayBuffer())
    // 生成不重复的文件名,避免重名覆盖
    const fileName = `${Date.now()}-${file.name.replace(/\s/g, '')}`
    // 拼接public文件夹下的存储路径
    const savePath = path.join(process.cwd(), 'public', 'uploads', fileName)
    
    // 确保uploads文件夹存在,不存在则递归创建
    const uploadDir = path.dirname(savePath)
    if (!fs.existsSync(uploadDir)) {
      fs.mkdirSync(uploadDir, { recursive: true })
    }

    // 写入文件到磁盘
    fs.writeFileSync(savePath, fileBuffer)

    // 返回可直接访问的图片相对路径
    return res.status(200).json({
      message: '上传成功',
      imageUrl: `/uploads/${fileName}`
    })
  } catch (error) {
    return res.status(500).json({ message: '上传失败', error: error.message })
  }
}

App Router 实现(app/api/upload/route.js)

App Router下的实现更简洁,不需要额外修改解析配置:

import fs from 'fs'
import path from 'path'
import { NextResponse } from 'next/server'

export async function POST(req) {
  try {
    const formData = await req.formData()
    const file = formData.get('myfile')
    if (!file) {
      return NextResponse.json({ message: '未接收到上传文件' }, { status: 400 })
    }

    if (!file.type.startsWith('image/')) {
      return NextResponse.json({ message: '仅支持上传图片文件' }, { status: 400 })
    }

    const fileBuffer = Buffer.from(await file.arrayBuffer())
    const fileName = `${Date.now()}-${file.name.replace(/\s/g, '')}`
    const savePath = path.join(process.cwd(), 'public', 'uploads', fileName)
    
    const uploadDir = path.dirname(savePath)
    if (!fs.existsSync(uploadDir)) {
      fs.mkdirSync(uploadDir, { recursive: true })
    }

    fs.writeFileSync(savePath, fileBuffer)

    return NextResponse.json({
      message: '上传成功',
      imageUrl: `/uploads/${fileName}`
    })
  } catch (error) {
    return NextResponse.json({ message: '上传失败', error: error.message }, { status: 500 })
  }
}
生产环境云存储方案

把上传文件存在public文件夹或者服务器本地磁盘完全不适合生产环境,正确的生产方案是使用专门的对象存储服务,通用实现逻辑如下:

  • 核心原则:应用服务器不持久化存储用户上传的静态文件,避免部署、扩容、实例故障时文件丢失,也能降低应用服务器的带宽压力
  • 实现步骤:
    • 开通任意一家云厂商的对象存储服务,创建公共读权限的存储桶,配置好跨域规则允许你的前端域名访问
    • 在Next.js API路由中接收到前端上传的文件对象后,不需要把文件写入本地磁盘,直接将文件流传给对应云服务的官方SDK
    • SDK上传完成后会返回文件的公网访问地址,你可以把这个地址和其他业务数据一起存在自己的数据库里
    • 前端需要展示图片时,直接读取数据库里存储的云存储URL即可
  • 额外安全建议:上传时增加校验逻辑,限制文件大小(比如普通展示类图片限制最大2MB)、校验文件真实类型,避免恶意用户上传可执行文件、超大文件占用服务资源。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 07:09:18