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

Next.js 14:客户端组件如何向服务器动作传递File对象?

问题:通过预签名URL上传文件到AWS S3的客户端-服务器组件交互问题

我正尝试通过预签名URL将图片/视频上传至AWS S3存储。从后端获取URL后,需将URL与File对象发送到服务器动作以执行AWS S3的PUT请求,但遇到以下问题:

  • 无法直接从客户端向服务器组件发送File对象
  • 用JSON.stringify处理File对象,得到空对象
  • 改用FormData发送,报错:"Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream"
  • 将数据转为ArrayBuffer再JSON.stringify,仍得到空对象

相关代码如下:

page.tsx(点击处理函数)

const clickHandler = async () => {
    const imageName = selected?.name;
    const imageType = selected?.type;

    const result = await createPreSignedObject({
      variables: { name: imageName, contentType: imageType },
    });
    const resultedResult = result.data.createPreSignedObject;
    console.log(resultedResult); //preSignedURL
    if (resultedResult && typeof resultedResult === "string") {
      if (selected) {
        //const formData = new FormData();
        //formData.append("file", selected);
        const fileToArrayBuffer = (file: File) => {
          return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = () => resolve(reader.result);
            reader.onerror = () => reject(reader.error);
            reader.readAsArrayBuffer(file);
          });
        };
        const fileBufferResult = await fileToArrayBuffer(selected);
        console.log(fileBufferResult);
        console.log(JSON.stringify(fileBufferResult)); //giving empty object
        await postObjectToS3(resultedResult, JSON.stringify(fileToArrayBuffer(selected)));
      }
    } else {
      console.log("no result found");
    }
  };

preSignedObjects.ts

"use server"
export const postObjectToS3 = async (url: string, selectedFile: string) => {
  const newResult = JSON.parse(selectedFile);
  console.log(newResult);
  const result = await axios.put(url, newResult, {
    headers: {
      "Content-Type": "multipart/form-data",
    },
  });
};

解决方案

核心思路

服务器组件不需要接收完整的File对象或其序列化形式,直接在客户端用预签名URL发起PUT请求是更简单的方案。如果必须通过服务器组件中转,需要正确传递文件的二进制数据,而非序列化后的空对象。

方案1:客户端直接发起PUT请求(推荐)

无需经过服务器组件中转,直接让客户端与S3交互,减少网络请求且避免File对象传递问题:

// page.tsx 点击处理函数修改
const clickHandler = async () => {
  const imageName = selected?.name;
  const imageType = selected?.type;

  const result = await createPreSignedObject({
    variables: { name: imageName, contentType: imageType },
  });
  const preSignedURL = result.data.createPreSignedObject;
  
  if (preSignedURL && selected) {
    try {
      const response = await fetch(preSignedURL, {
        method: 'PUT',
        body: selected, // 直接传入File对象
        headers: {
          'Content-Type': imageType || '', // 必须匹配预签名URL生成时指定的Content-Type
        },
      });
      
      response.ok ? console.log('文件上传成功') : console.error('上传失败', await response.text());
    } catch (err) {
      console.error('请求出错', err);
    }
  } else {
    console.log('未获取到预签名URL或选中文件');
  }
};

方案2:通过服务器组件中转(若业务必须)

如果必须经过服务器组件,需将文件转为Base64字符串传递,再在服务器端转回二进制数据:

客户端代码修改(page.tsx)

const clickHandler = async () => {
  const imageName = selected?.name;
  const imageType = selected?.type;

  const result = await createPreSignedObject({
    variables: { name: imageName, contentType: imageType },
  });
  const preSignedURL = result.data.createPreSignedObject;
  
  if (preSignedURL && selected) {
    // 将File转为Base64字符串
    const fileToBase64 = (file: File) => {
      return new Promise<string>((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => resolve(reader.result as string);
        reader.onerror = () => reject(reader.error);
        reader.readAsDataURL(file);
      });
    };
    
    const base64Data = await fileToBase64(selected);
    // 移除data URL前缀,仅保留Base64内容
    const pureBase64 = base64Data.split(',')[1];
    
    await postObjectToS3(preSignedURL, pureBase64, imageType || '');
  } else {
    console.log('未获取到预签名URL或选中文件');
  }
};

服务器组件代码修改(preSignedObjects.ts)

"use server"
import axios from 'axios';

export const postObjectToS3 = async (url: string, base64File: string, contentType: string) => {
  // 将Base64转为Buffer
  const fileBuffer = Buffer.from(base64File, 'base64');
  
  try {
    const result = await axios.put(url, fileBuffer, {
      headers: {
        'Content-Type': contentType, // 必须和预签名URL生成时的Content-Type一致
      },
    });
    console.log('上传成功', result);
  } catch (err) {
    console.error('上传失败', err);
  }
};

关键注意事项

  • Content-Type必须严格匹配:预签名URL生成时指定的contentType,必须与PUT请求的Content-Type header一致,否则S3会返回403错误
  • 禁止序列化File对象:File是浏览器特有类型,无法通过JSON序列化传递,必须转为二进制或Base64格式
  • 优先选择客户端直接上传:减少中转能提升性能,同时避免数据传递的各类问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 07:44:54