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

如何通过Next.js/Node.js/TS正确上传文件至Amazon S3?

正确实现Next.js + TypeScript 上传图片到S3的方案

一、原代码的问题分析

  • 未触发实际上传:s3.upload()返回的是ManagedUpload对象而非直接Promise,必须调用.promise()才能通过await等待上传完成,原代码缺少这一步,导致代码执行完但上传未真正触发。
  • 密钥暴露风险:前端直接嵌入IAM密钥会导致密钥泄露,这是AWS严格禁止的,也是旧上传方式被弃用的核心原因。
  • 类型不严谨:selectedFile用any类型,建议改为File | null提升代码健壮性。
  • 权限与CORS问题:需确保IAM用户拥有s3:PutObject权限,且存储桶CORS配置允许前端域名的PUT请求。

二、推荐方案:预签名URL上传(符合AWS最佳实践)

预签名URL由后端(Next.js API路由)生成临时上传凭证,前端通过该URL直接向S3发起PUT请求,全程不暴露IAM密钥,安全且合规。

步骤1:配置IAM权限与S3 CORS

  1. IAM权限策略:给你的IAM用户添加以下权限(替换your-bucket-name):
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    },
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    }
  ]
}
  1. S3 CORS配置:在存储桶「权限」→「跨域资源共享(CORS)」中添加规则(允许前端域名的PUT/GET请求):
[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["PUT", "GET"],
    "AllowedOrigins": ["http://localhost:3000"], // 生产环境替换为你的前端域名
    "ExposeHeaders": []
  }
]

步骤2:创建Next.js API路由生成预签名URL

使用AWS SDK v3(v2已弃用),在pages/api/s3-presigned-url.ts(App Router对应app/api/s3-presigned-url/route.ts)编写后端逻辑:

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import type { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== "POST") return res.status(405).json({ message: "Method not allowed" });

  const { fileName, fileType } = req.body;
  if (!fileName || !fileType) return res.status(400).json({ message: "Missing fileName or fileType" });

  const s3Client = new S3Client({
    region: process.env.S3_REGION as string,
    credentials: {
      accessKeyId: process.env.S3_ACCESS_KEY as string,
      secretAccessKey: process.env.S3_SECRET_KEY as string,
    },
  });

  try {
    const command = new PutObjectCommand({
      Bucket: process.env.S3_BUCKET_NAME as string,
      Key: fileName,
      ContentType: fileType,
    });
    const signedUrl = await getSignedUrl(s3Client, command, { expiresIn: 300 }); // URL有效期300秒
    res.status(200).json({ signedUrl });
  } catch (error) {
    console.error("生成预签名URL失败:", error);
    res.status(500).json({ message: "Failed to generate presigned URL" });
  }
}

步骤3:前端上传逻辑(TypeScript)

修改组件代码,先获取预签名URL再上传文件:

import { useState } from "react";
import { SubmitHandler } from "react-hook-form";
import type { RegisterFormSchemaType } from "./types";

export default function UploadForm() {
  const [selectedFile, setSelectedFile] = useState<File | null>(null);

  const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (file) setSelectedFile(file);
  };

  const onSubmit: SubmitHandler<RegisterFormSchemaType> = async (data) => {
    if (!selectedFile) return;

    try {
      // 1. 获取预签名URL
      const presignedRes = await fetch("/api/s3-presigned-url", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          fileName: `uploads/${Date.now()}-${selectedFile.name}`, // 加前缀避免文件名冲突
          fileType: selectedFile.type,
        }),
      });
      const { signedUrl } = await presignedRes.json();

      // 2. 直接上传到S3
      const uploadRes = await fetch(signedUrl, {
        method: "PUT",
        body: selectedFile,
        headers: { "Content-Type": selectedFile.type },
      });

      uploadRes.ok ? console.log("上传成功") : console.error("上传失败");
    } catch (error) {
      console.error("上传出错:", error);
    }
  };

  return (
    <form onSubmit={onSubmit}>
      <input type="file" onChange={handleFileUpload} accept="image/*" />
      <button type="submit">上传图片</button>
    </form>
  );
}

步骤4:配置环境变量

在项目根目录.env.local中添加:

S3_ACCESS_KEY=你的IAM访问密钥
S3_SECRET_KEY=你的IAM秘密密钥
S3_REGION=存储桶区域(如us-east-1)
S3_BUCKET_NAME=你的存储桶名称

三、预签名URL的优势

  • 安全:IAM密钥仅在后端使用,不会暴露给前端用户。
  • 合规:基于AWS SDK v3,符合官方最新规范。
  • 可控:可自定义URL有效期、上传路径、文件类型等规则。

四、常见问题排查

  • CORS错误:检查S3 CORS配置是否允许前端域名的PUT请求。
  • 预签名URL生成失败:确认IAM权限包含s3:PutObject,环境变量配置正确。
  • 上传失败:检查文件类型是否匹配、URL是否过期、文件名是否含特殊字符。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 23:23:15