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
相关产品推荐
相关产品推荐

