
本文详解在 next.js app router 中,如何通过服务端组件预取 api 数据并作为 props 传入客户端组件,避免重复请求、提升首屏性能,并规避“objects are not valid as a react child”等常见错误。
本文详解在 next.js app router 中,如何通过服务端组件预取 api 数据并作为 props 传入客户端组件,避免重复请求、提升首屏性能,并规避“objects are not valid as a react child”等常见错误。
在 Next.js 的 App Router 架构中,服务端组件(Server Components)天然支持异步数据获取,而客户端组件(Client Components)则负责交互逻辑与动态行为。一个典型需求是:在首页(page.js)中一次性从 API 获取结构化内容(如富文本区块),缓存于服务端,再安全地传递给客户端组件进行渲染——既利用服务端缓存优势,又保留客户端的交互能力。
但需注意:服务端组件不能直接“返回 props 对象”。你遇到的错误:
Error: Objects are not valid as a React child (found: object with keys {props})正是因为在 ServerComponent.js 中错误地返回了 { props: { data } } 这样的纯对象,而非有效的 React 元素。React 组件的返回值必须是 JSX、null、undefined 或可遍历的 React 元素集合,绝不能是裸对象。
✅ 正确做法是:将数据获取逻辑直接内聚到页面级服务端组件(即 page.js)中,通过 async/await 预取数据,再以标准 props 形式传入客户端组件。这是 Next.js 官方推荐且最简洁高效的方式。
✅ 推荐实现:在 page.js 中预取 + 透传
// app/page.tsx
import Home from './clientComponent';
// ✅ 页面组件必须声明为 async 才能使用 await
export default async function Page() {
// 在服务端完成 API 请求(自动缓存、SSR)
const data = await getData();
return <Home data={data} />;
}
// 独立的数据获取函数(可复用、可测试)
async function getData() {
const res = await fetch('https://api.example.com/pages/1', {
next: { revalidate: 3600 }, // 可选:每小时重新验证缓存
});
if (!res.ok) {
throw new Error(`API request failed: ${res.status}`);
}
return res.json();
}✅ 客户端组件接收并安全渲染
// app/clientComponent.tsx
'use client';
import { Fragment } from 'react';
import Image from 'next/image';
import { Typography } from '@mui/material'; // 假设使用 MUI
import styles from './page.module.css';
export default function Home({ data }: { data: any }) {
// ✅ 安全访问嵌套属性(建议添加类型守卫或使用 optional chaining)
const set1 = data?.textblockset?.find((item: any) => item.id === 1);
if (!set1 || !Array.isArray(set1.textblock)) {
return <main className={styles.main}>加载中或数据异常</main>;
}
return (
<main className={styles.main}>
{set1.textblock.map((item: any, index: number) => (
<Fragment key={item.id || index}>
{item.block_icon && (
<Image
src={item.block_icon}
alt="icon"
width={50}
height={50}
loading="lazy"
className={styles.icon}
/>
)}
<Typography paragraph fontWeight="bold">
{item.block_title}
</Typography>
<Typography>{item.block_content}</Typography>
</Fragment>
))}
</main>
);
}⚠️ 关键注意事项
- page.js 必须是 async 函数:否则 await getData() 会报语法错误。Next.js 会自动将其视为服务端组件并处理 SSR。
- 不要创建中间服务端组件包装器(如原方案中的 <Server />):它无法“返回 props”,只能返回 JSX;若强行返回对象,React 会立即报错。
- 类型安全建议:为 data 添加 TypeScript 接口(如 PageData),并在客户端组件中使用 data?.textblockset?.[0] 等可选链,避免运行时崩溃。
- 缓存控制:在 fetch 选项中使用 next: { revalidate: N } 显式声明缓存策略,充分利用 Next.js 的服务端缓存能力。
- 客户端组件必要性评估:如果 Home 组件不涉及 useState、useEffect、事件监听等客户端专属逻辑,应优先考虑改为服务端组件——更轻量、更快、更安全。
✅ 总结
将数据获取逻辑上提至 page.tsx(服务端组件),通过 async/await 预取并直接传参给客户端组件,是 Next.js App Router 中最符合直觉、最稳定可靠的数据流模式。它规避了组件间非法对象传递的风险,充分发挥了服务端缓存与客户端交互的各自优势。记住核心原则:数据获取在服务端,状态与交互在客户端,传递靠 props,边界要清晰。









