NextJs 13/14中如何在客户端展示Server Action执行进度?
Next.js 13/14 Server Action 实时反馈执行进度方案
Server Action本身是单次请求响应模式,无法直接流式返回执行进度,需要结合额外的通信机制实现实时状态反馈,以下是两种可行方案:
方案一:Server-Sent Events (SSE) 单向推送进度
SSE是最适配进度更新场景的方案,支持服务器主动向客户端推送消息,无需客户端频繁发起请求。
实现步骤
- 客户端触发任务时生成唯一
taskId,同时开启SSE连接监听进度 - 服务器端处理数据库记录时,实时更新对应
taskId的进度数据 - SSE接口定期将进度推送给客户端,客户端同步更新UI
代码示例
客户端组件(use client)
'use client'; import { useState } from 'react'; export default function ProgressDemo() { const [progress, setProgress] = useState(0); const [isRunning, setIsRunning] = useState(false); const startTask = async () => { setIsRunning(true); setProgress(0); // 生成唯一任务ID const taskId = crypto.randomUUID(); // 建立SSE连接 const eventSource = new EventSource(`/api/progress?taskId=${taskId}`); eventSource.onmessage = (event) => { const { progress: currentProgress, done } = JSON.parse(event.data); setProgress(currentProgress); if (done) { eventSource.close(); setIsRunning(false); } }; eventSource.onerror = () => { eventSource.close(); setIsRunning(false); alert('任务执行出错'); }; // 触发数据处理接口 await fetch('/api/process-records', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ taskId }) }); }; return ( <div className="p-4"> <h2>批量处理记录进度</h2> {isRunning ? ( <> <div className="w-full h-4 bg-gray-200 rounded-full overflow-hidden mt-2"> <div className="h-full bg-blue-600 transition-all duration-100" style={{ width: `${progress}%` }} /> </div> <p className="mt-2">已完成:{progress}%</p> </> ) : ( <button onClick={startTask} disabled={isRunning} className="px-4 py-2 bg-blue-600 text-white rounded" > 开始处理1000条记录 </button> )} </div> ); }
数据处理API(app/api/process-records/route.ts)
import { NextResponse } from 'next/server'; // 生产环境建议用Redis替代内存Map const taskProgressStore = new Map<string, { progress: number; done: boolean }>(); export async function POST(request: Request) { const { taskId } = await request.json(); taskProgressStore.set(taskId, { progress: 0, done: false }); try { const total = 1000; for (let i = 0; i < total; i++) { // 替换为你的实际数据库操作逻辑 // await db.record.update(...) // 更新进度 const currentProgress = Math.round(((i + 1) / total) * 100); taskProgressStore.set(taskId, { progress: currentProgress, done: i === total - 1 }); // 模拟处理耗时 await new Promise(resolve => setTimeout(resolve, 10)); } return NextResponse.json({ success: true }); } catch (error) { taskProgressStore.set(taskId, { progress: 0, done: true }); return NextResponse.json({ success: false }, { status: 500 }); } }
SSE进度推送API(app/api/progress/route.ts)
import { NextResponse } from 'next/server'; import { taskProgressStore } from '../process-records/route'; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const taskId = searchParams.get('taskId'); if (!taskId) { return NextResponse.json({ error: '缺少任务ID' }, { status: 400 }); } const stream = new ReadableStream({ async start(controller) { const encoder = new TextEncoder(); // 每100ms推送一次进度 const interval = setInterval(() => { const progressData = taskProgressStore.get(taskId); if (!progressData) { controller.close(); clearInterval(interval); return; } // 发送SSE格式的消息 controller.enqueue(encoder.encode(`data: ${JSON.stringify(progressData)}\n\n`)); if (progressData.done) { controller.close(); clearInterval(interval); taskProgressStore.delete(taskId); // 清理过期任务 } }, 100); // 客户端断开连接时清理资源 request.signal.addEventListener('abort', () => { clearInterval(interval); controller.close(); taskProgressStore.delete(taskId); }); } }); return new NextResponse(stream, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', }, }); }
方案二:客户端轮询进度
如果不想引入SSE,也可以用简单的轮询方案,适合小体量任务:
- 客户端触发任务后,每隔固定时间(比如500ms)请求进度接口
- 服务器返回对应
taskId的进度数据,直到任务完成 - 客户端根据返回结果更新UI
该方案实现简单,但频繁请求会增加服务器压力,不适合大规模任务场景。
注意事项
- 生产环境存储:示例中用内存Map存储进度,多实例部署时会丢失数据,建议用Redis或数据库持久化任务进度
- 连接稳定性:SSE可能存在超时断开的情况,客户端需添加重连逻辑
- 资源清理:任务完成或客户端断开后,要及时清理服务器端的进度缓存,避免内存泄漏
内容的提问来源于stack exchange,提问作者Gerald Fuchs
相关产品推荐
相关产品推荐

