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

如何在Next.js 14中用Fetch编程式调用React Server Actions模拟限流?

问题分析与解决方案

你遇到的核心问题是:直接用Fetch POST到页面路由/signup时,Next.js会返回页面HTML,而非Server Action的处理结果——这是因为Server Action并非独立的/api路由,它和页面组件绑定,需要特定的请求标识才能触发。

为什么返回HTML?

当你向/signup发送POST请求时,Next.js默认会将其视为页面渲染请求,返回该路由对应的页面HTML,而不会触发Server Action。想要触发Server Action,必须让请求带上Next.js能识别的Action调用标识。

用原生Fetch触发Server Action的正确方式

1. 确保Server Action的定义正确

首先你的Server Action必须用'use server'标记,示例:

// app/signup/actions.ts
'use server';

export async function signupAction(formData: FormData) {
  const email = formData.get('email') as string;
  // 写入数据库逻辑
  return { success: true, email };
}

2. 构造正确的Fetch请求

在外部Node脚本中,你需要添加特定请求头,告诉Next.js这是一个Action调用:

async function main() {
  const formData = new FormData();
  formData.append('email', 'hey@yo.com');

  const res = await fetch('http://localhost:3000/signup', {
    method: 'POST',
    body: formData,
    headers: {
      // 指定要触发的Server Action路径
      'Next-Action': 'app/signup/actions.ts/signupAction',
      // 必须添加的标识头,区分页面请求与Action调用
      'Next-Router-State-Tree': '{}',
      'Next-Action-State': '{}'
    }
  });

  if (res.ok) {
    const data = await res.json();
    console.log({ data });
  } else {
    // 若仍返回HTML,可打印响应文本排查问题
    const html = await res.text();
    console.error('返回HTML:', html);
  }
}

main();

3. 模拟限流测试

要模拟限流,你可以用循环批量发起请求,同时控制并发数避免瞬间压垮服务器:

// 控制并发请求数
const CONCURRENT_LIMIT = 5;
// 总测试请求数
const TOTAL_REQUESTS = 100;

async function sendSignupRequest(email: string) {
  const formData = new FormData();
  formData.append('email', email);

  try {
    const res = await fetch('http://localhost:3000/signup', {
      method: 'POST',
      body: formData,
      headers: {
        'Next-Action': 'app/signup/actions.ts/signupAction',
        'Next-Router-State-Tree': '{}',
        'Next-Action-State': '{}'
      }
    });
    const data = await res.json();
    console.log(`请求成功: ${email}`, data);
  } catch (err) {
    console.error(`请求失败: ${email}`, err);
  }
}

async function main() {
  // 生成所有请求任务
  const requests = Array.from({ length: TOTAL_REQUESTS }, (_, i) => 
    sendSignupRequest(`test-${i}@example.com`)
  );

  // 分批执行,控制并发
  for (let i = 0; i < requests.length; i += CONCURRENT_LIMIT) {
    const batch = requests.slice(i, i + CONCURRENT_LIMIT);
    await Promise.all(batch);
    // 每批请求间添加延迟,模拟真实用户操作间隔
    await new Promise(resolve => setTimeout(resolve, 100));
  }
}

main();

替代方案:改用Route Handler(/api路由)

如果觉得Server Action的调用方式过于繁琐,你可以直接把signup逻辑改成/api路由,这样就能像普通接口一样用Fetch调用:

// app/api/signup/route.ts
export async function POST(request: Request) {
  const formData = await request.formData();
  const email = formData.get('email') as string;
  // 写入数据库逻辑
  return Response.json({ success: true, email });
}

对应的Fetch代码可以简化为:

async function main() {
  const formData = new FormData();
  formData.append('email', 'hey@yo.com');

  const res = await fetch('http://localhost:3000/api/signup', {
    method: 'POST',
    body: formData,
  });

  const data = await res.json();
  console.log({ data });
}

main();

这种方式更符合你对/api路由的使用习惯,模拟限流也更直接。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 02:20:10