基于Server Actions的多文件上传真实进度条实现咨询
用Server Actions实现真实上传进度条的方案
核心问题
Server Actions默认的调用方式(如formAction、直接函数调用)被Next.js封装,无法直接获取上传进度事件。要实现真实进度,需手动构造请求并通过XMLHttpRequest监听上传进度(Fetch API暂不支持可靠的上传进度监听)。
步骤1:编写支持流式处理的Server Action
用busboy处理文件上传,避免一次性读入内存,确保进度跟踪真实有效:
// app/actions/upload.ts 'use server'; import busboy from 'busboy'; import { createWriteStream } from 'fs'; import { join } from 'path'; import { tmpdir } from 'os'; import { Readable } from 'stream'; export async function uploadFiles(request: Request) { return new Promise((resolve, reject) => { const bb = busboy({ headers: request.headers }); // 处理每个上传的文件 bb.on('file', (_name, fileStream, { filename }) => { const savePath = join(tmpdir(), filename); fileStream.pipe(createWriteStream(savePath)); fileStream.on('end', () => { console.log(`文件 ${filename} 已写入临时目录`); }); }); // 所有文件处理完成 bb.on('finish', () => { resolve({ success: true, message: '全部文件上传完成' }); }); // 处理错误 bb.on('error', (err) => { reject(new Error(`上传失败: ${err.message}`)); }); // 将请求体流转给busboy Readable.fromWeb(request.body as ReadableStream).pipe(bb); }); }
步骤2:前端用XMLHttpRequest实现进度监听
放弃默认的Server Action调用方式,手动构造请求,通过xhr.upload.onprogress跟踪真实进度:
// app/upload/page.tsx 'use client'; import { useState } from 'react'; export default function UploadPage() { const [uploadProgress, setUploadProgress] = useState(0); const handleUpload = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); const form = e.currentTarget; const fileInput = form.querySelector('input[type="file"]'); if (!fileInput?.files || fileInput.files.length === 0) return; // 重置进度 setUploadProgress(0); // 构造请求数据 const formData = new FormData(); formData.append('__NEXT_ACTION__', JSON.stringify({ action: 'app/actions/upload/uploadFiles', // Server Action的完整路径 args: [], caller: 'client', })); // 添加所有选中的文件 Array.from(fileInput.files).forEach(file => { formData.append('files', file); }); // 初始化XMLHttpRequest const xhr = new XMLHttpRequest(); xhr.open('POST', '/api/_next/server-action'); // 监听上传进度 xhr.upload.addEventListener('progress', (event) => { if (event.lengthComputable) { const percent = Math.round((event.loaded / event.total) * 100); setUploadProgress(percent); } }); // 监听请求完成 xhr.addEventListener('load', () => { if (xhr.status === 200) { const result = JSON.parse(xhr.responseText); alert(result.message); } else { alert('上传失败,请重试'); } setUploadProgress(0); }); // 监听请求错误 xhr.addEventListener('error', () => { alert('上传出错,请检查网络'); setUploadProgress(0); }); // 发送请求 xhr.send(formData); }; return ( <div className="upload-container"> <form onSubmit={handleUpload}> <input type="file" multiple name="files" /> <button type="submit">开始上传</button> </form> {/* 进度条 */} <div className="progress-wrapper"> <div className="progress-bar" style={{ width: `${uploadProgress}%` }} ></div> </div> <p>{uploadProgress}%</p> </div> ); }
关键注意事项
- Server Action路径:
__NEXT_ACTION__中的action字段要和你的Server Action文件路径完全匹配(如app/actions/upload.ts中的uploadFiles对应app/actions/upload/uploadFiles)。 - 流式处理:必须用流式方式处理文件(如
busboy),如果直接用await file.arrayBuffer()会一次性读取整个文件,进度条会直接跳到100%。 - XMLHttpRequest的必要性:Fetch API目前没有原生支持上传进度的可靠方案,XMLHttpRequest是当前最兼容的选择。
- 依赖安装:如果用
busboy,需要安装依赖:npm install busboy。
内容的提问来源于stack exchange,提问作者MicCheck-
相关产品推荐
相关产品推荐

