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

求基于NextJS 13.4 App Router与AWS SDK v3的S3预签名URL上传示例

NextJS 13.4 App Router + AWS SDK v3 S3预签名URL上传示例

1. 安装依赖

npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

2. 配置环境变量

在项目根目录创建.env.local文件,添加以下内容:

AWS_ACCESS_KEY_ID=你的AWS访问密钥ID
AWS_SECRET_ACCESS_KEY=你的AWS秘密访问密钥
AWS_REGION=你的S3桶所在区域(比如us-east-1)
S3_BUCKET_NAME=目标S3桶名称

3. 创建App Router API路由

新建app/api/s3/presigned-url/route.ts,实现预签名URL生成接口:

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

const s3Client = new S3Client({
  region: process.env.AWS_REGION!,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
});

export async function POST(request: Request) {
  try {
    const { fileName, fileType } = await request.json();

    const command = new PutObjectCommand({
      Bucket: process.env.S3_BUCKET_NAME!,
      Key: fileName, // 可自定义存储路径,比如`uploads/${fileName}`
      ContentType: fileType,
    });

    // 生成有效期15分钟的预签名URL
    const signedUrl = await getSignedUrl(s3Client, command, { expiresIn: 900 });

    return NextResponse.json({ signedUrl });
  } catch (error) {
    console.error("生成预签名URL失败:", error);
    return NextResponse.json({ error: "生成预签名URL失败" }, { status: 500 });
  }
}

4. 前端上传组件示例

创建一个简单的上传组件components/S3Uploader.tsx:

"use client";

import { useState } from "react";

export default function S3Uploader() {
  const [file, setFile] = useState<File | null>(null);
  const [uploading, setUpLoading] = useState(false);

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

  const handleUpload = async () => {
    if (!file) return;

    setUpLoading(true);
    try {
      // 第一步:获取预签名URL
      const presignRes = await fetch("/api/s3/presigned-url", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          fileName: file.name,
          fileType: file.type,
        }),
      });

      const { signedUrl } = await presignRes.json();

      // 第二步:用预签名URL上传文件到S3
      const uploadRes = await fetch(signedUrl, {
        method: "PUT",
        body: file,
        headers: { "Content-Type": file.type },
      });

      if (uploadRes.ok) {
        alert("文件上传成功");
        setFile(null);
      } else {
        throw new Error("S3上传失败");
      }
    } catch (error) {
      console.error("上传失败:", error);
      alert("上传失败,请重试");
    } finally {
      setUpLoading(false);
    }
  };

  return (
    <div>
      <input type="file" onChange={handleFileChange} disabled={uploading} />
      <button onClick={handleUpload} disabled={!file || uploading}>
        {uploading ? "上传中..." : "上传到S3"}
      </button>
    </div>
  );
}

关键注意事项

  • S3桶CORS配置:需在S3控制台配置CORS规则,允许PUT请求:
    [
      {
        "AllowedHeaders": ["*"],
        "AllowedMethods": ["PUT"],
        "AllowedOrigins": ["你的NextJS域名,比如http://localhost:3000"],
        "ExposeHeaders": []
      }
    ]
    
  • IAM权限:确保你的AWS IAM用户拥有s3:PutObject权限,策略示例:
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::你的桶名称/*"
        }
      ]
    }
    
  • 安全建议:生产环境中避免直接暴露IAM密钥,可考虑使用AWS IAM Roles for Service Accounts(如果部署在ECS/EKS)或Vercel的环境变量加密;同时可对文件名添加前缀(比如用户ID)避免冲突,限制允许上传的文件类型和大小。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 19:22:32