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

Next.js 13 App Router:客户端组件调用服务端动作失败为何不渲染error.tsx?

问题

我用Next.js 13的App Router做了一个React服务端组件(RSC)页面,页面里包含一个叫MyForm的客户端组件,这个组件渲染Formik表单。

表单提交时,通过Formik的onSubmit触发服务端动作提交数据:

  • 成功的话,服务端动作执行重定向
  • 失败的话,服务端动作抛出错误

我原本以为和RSC中抛出错误一样,错误对象会传给最近的error.tsx渲染,但实际客户端组件调用服务端动作失败时,最近的error.tsx根本没触发。这是为什么?

我看到用useTransition钩子能实现预期效果,还试过下面的写法,确实能用,但搞不懂原理:

app/components/MyForm

"use client"

export default function MyForm(){
        // 无关代码省略

    return (
        <Formik
            initialValues={{ email: '' }}
            id="register"
            onSubmit={async (values) => {
                await myServerAction(values);
            }}
        >
            <Field type="email" name="email" placeholder="Email" />
            <button type="submit">Submit</button>
        </Formik>
    );
};

app/page.tsx

export default async function Page(props) {
    // 这里是服务端环境
    // 如果someFetch抛出错误,会传递给最近的error.js

    const { data } = someFetch(url);

    return (
        <div>
            <SomeCompenent data={data} /> {/* 无关组件 */}
                        
            <MyForm />
        </div>
    );
}
原因分析

Next.js的error.tsx只处理服务端组件在初始渲染或服务端数据获取阶段抛出的错误。当客户端组件调用服务端动作时,这个调用是浏览器端发起的异步请求,属于客户端运行时操作,不属于RSC初始渲染流程,所以错误不会自动冒泡到error.tsx。

服务端动作在客户端调用时,本质是通过fetch请求触发的,抛出的错误会被客户端异步代码捕获(比如你代码里的await myServerAction()),但Next.js不会自动把这个错误关联到页面的error.tsx边界——因为error.tsx是为服务端渲染的错误边界设计的,不是客户端异步操作的错误边界。

解决方案与原理

用useTransition包裹服务端动作的调用,就能让错误触发error.tsx,核心原因是:

  • useTransition是React的并发特性,当在客户端用它触发服务端动作时,Next.js会把这个操作和页面的服务端渲染状态关联起来,将服务端动作的错误纳入到页面的错误边界处理流程中。
  • 具体来说,useTransition会标记这个更新为“过渡状态”,Next.js在处理这个状态时,会把服务端动作抛出的错误传递给最近的error.tsx,就像RSC初始渲染时的错误一样。

修改后的MyForm组件示例:

"use client"

import { useTransition } from 'react';
import { Formik, Field } from 'formik';
import myServerAction from '../actions/myServerAction';

export default function MyForm(){
  const [isPending, startTransition] = useTransition();

  return (
    <Formik
      initialValues={{ email: '' }}
      id="register"
      onSubmit={(values) => {
        startTransition(async () => {
          await myServerAction(values);
        });
      }}
    >
      <Field type="email" name="email" placeholder="Email" />
      <button type="submit" disabled={isPending}>
        {isPending ? '提交中...' : '提交'}
      </button>
    </Formik>
  );
};

原理拆解:

  • startTransition会把内部的异步调用包装成React的并发更新,Next.js识别到这个更新是触发服务端动作后,会将服务端返回的错误路由到页面的error.tsx边界。
  • 同时isPending状态可以用来处理提交中的加载状态,提升用户体验。

内容的提问来源于stack exchange,提问作者van der dev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 04:25:55