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

Next.js 13.4+ SSR与客户端组件协同渲染异常排查与解决

Next.js 13.4+ SSR与客户端组件协同方案及渲染边界配置

错误本质分析

你遇到的async/await is not yet supported in Client Components错误,核心原因并非事件处理函数里的async/await本身不被允许,而是以下两种常见问题:

  1. 把客户端组件本身定义成了async函数(客户端组件必须是同步函数)
  2. Server Action的调用场景不符合规范(必须在事件处理函数而非渲染阶段调用)

渲染边界正确配置规范

1. 路由页面(Server Component)

  • 所有page.tsx/layout.tsx默认是SSR组件,无需添加任何渲染标记
  • 负责在服务器端获取公共数据、生成元数据,传递给子组件
// /services/[serviceId]/page.tsx
import ServicesHomePage from '@/components/ServicesHomePage';

export async function generateMetadata({ params }: { params: { serviceId: string } }) {
  // 服务器端生成页面元数据
  const service = await fetch(`/api/services/${params.serviceId}`, { cache: 'force-cache' }).then(res => res.json());
  return { title: service.name };
}

export default async function ServicePage({ params }: { params: { serviceId: string } }) {
  // 服务器端获取服务详情(SSR数据)
  const service = await fetch(`/api/services/${params.serviceId}`, { cache: 'force-cache' }).then(res => res.json());

  // 传递数据给客户端组件
  return <ServicesHomePage service={service} />;
}

2. 客户端组件

  • 仅在需要交互逻辑(如表单、状态管理)的组件文件顶部添加'use client'标记
  • 组件本身必须是同步函数,async/await只能用在事件处理、useEffect等异步回调中
// components/ServicesHomePage.tsx
'use client';

import PatientSearchBar from './PatientSearchBar';

export default function ServicesHomePage({ service }: { service: Service }) {
  return (
    <div>
      <h1>{service.name}</h1>
      <PatientSearchBar />
    </div>
  );
}

3. Server Action(服务器端逻辑)

  • 所有需要服务器端执行的敏感操作(数据库查询、内部API调用)单独放在标记'use server'的文件中
  • 客户端组件仅能在事件处理函数中调用这些Action
// actions/patientActions.ts
'use server';

export async function getPatientsByLastName(lastName: string) {
  // 服务器端执行查询逻辑(避免前端暴露敏感接口)
  const res = await fetch(`/internal-api/patients?lastName=${lastName}`, { cache: 'no-store' });
  if (!res.ok) throw new Error('获取患者数据失败');
  return res.json();
}

表单提交错误修复方案

针对PatientSearchBar的Formik提交逻辑,修正如下:

// components/PatientSearchBar.tsx
'use client';

import { useFormik } from 'formik';
import { getPatientsByLastName } from '@/actions/patientActions';

// 注意:组件本身是同步函数,不能加async
export default function PatientSearchBar() {
  const formik = useFormik({
    initialValues: { lastName: '' },
    // 仅在onSubmit事件处理函数中使用async/await
    onSubmit: async (values) => {
      try {
        const patients = await getPatientsByLastName(values.lastName);
        // 处理返回的患者数据(如更新本地状态)
      } catch (err) {
        // 处理错误提示
        console.error(err);
      }
    },
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      <input
        type="text"
        name="lastName"
        onChange={formik.handleChange}
        value={formik.values.lastName}
        placeholder="输入患者姓氏"
      />
      <button type="submit">搜索患者</button>
    </form>
  );
}

常见避坑点

  • 不要给Server Component添加'use client'标记,否则会丢失SSR能力
  • 禁止在客户端组件的渲染阶段(组件顶层、useMemo/useCallback的同步回调中)调用Server Action
  • 客户端组件不能定义为async函数,仅事件处理、异步钩子内可使用async/await

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 11:30:17