
本文详解如何在 next.js 13+ app router 中,通过服务端组件(server component)预获取 api 数据,并安全、高效地将结构化数据作为 props 传递给客户端组件(client component),避免“objects are not valid as a react child”等常见错误。
本文详解如何在 next.js 13+ app router 中,通过服务端组件(server component)预获取 api 数据,并安全、高效地将结构化数据作为 props 传递给客户端组件(client component),避免“objects are not valid as a react child”等常见错误。
在 Next.js 的 App Router 架构中,服务端组件(Server Components)默认支持异步数据获取,而客户端组件(Client Components)则负责交互逻辑与状态管理。一个典型需求是:在首屏加载时复用已缓存的 API 数据(如 CMS 内容),而非每次在浏览器端重复请求。实现这一目标的关键在于——让数据获取发生在服务端,并以纯 JSON 可序列化的 props 形式向下透传,而非尝试“渲染服务端组件作为子元素”或返回非 JSX 对象。
你遇到的报错:
Error: Objects are not valid as a React child (found: object with keys {props})根本原因在于 ServerComponent.js 中错误地返回了一个普通 JavaScript 对象 { props: { data } },而 React 期望组件函数返回的是合法的 JSX 元素(如 <div>、<Fragment> 等),而非裸对象。服务端组件不能也不应返回 props 对象;它应当直接渲染 UI,或——更常见地——被省略,由父级服务端组件统一获取数据并注入子组件。
✅ 正确做法:将数据获取逻辑上提至页面根组件(page.js),利用其 async 特性预取数据,并以标准 props 方式传入客户端组件。这是 Next.js 官方推荐的数据流模式,兼顾性能、缓存控制与类型安全。
以下是优化后的完整实现:
app/page.js(服务端组件,自动启用数据缓存)
// app/page.js
import Home from './clientComponent';
// ✅ 页面组件必须声明为 async 才能使用 await
export default async function Page() {
const data = await getData();
// ✅ 直接将解析后的 JSON 数据作为 props 传入客户端组件
return <Home data={data} />;
}
// 数据获取函数(可提取到 utils/api.js)
async function getData() {
const res = await fetch('https://api.example.com/pages/1', {
next: { revalidate: 3600 }, // ⚠️ 启用 ISR 缓存:每小时重新验证
});
if (!res.ok) {
throw new Error(`Failed to fetch page data: ${res.status}`);
}
return res.json(); // ✅ 返回 plain object,可安全序列化为 props
}app/clientComponent.js(客户端组件)
'use client'; // ✅ 必须显式声明
import { Fragment } from 'react';
import Image from 'next/image';
import Typography from '@mui/material/Typography';
import styles from './page.module.css';
export default function Home({ data }) {
// ✅ 数据已在服务端获取并序列化,此处可直接消费
const set1 = data.textblockset?.find(item => item.id === 1);
if (!set1 || !Array.isArray(set1.textblock)) {
return <main className={styles.main}>加载中或内容为空</main>;
}
return (
<main className={styles.main}>
{set1.textblock.map((item) => (
<Fragment key={item.id}>
{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>
);
}? 关键注意事项与最佳实践:
- 不要在服务端组件中返回非 JSX 对象:return { props: { ... } } 是无效模式,React 不会将其解释为 props,而是尝试渲染该对象 → 触发报错。
- page.js 天然是服务端组件:无需额外包装或嵌套,直接 async + await 即可,且自动继承 Next.js 的数据缓存策略(如 fetch(..., { next: { revalidate } }))。
- 客户端组件接收的 props 必须可序列化:确保 data 是纯 JSON 类型(object/array/string/number/boolean/null),不含 Date、Map、Function 或自定义类实例。若需转换,应在服务端完成(例如 new Date().toISOString())。
- 错误边界建议:在客户端组件中添加空值校验(如 ?. 和 Array.isArray()),避免因服务端数据结构变动导致渲染崩溃。
- 性能提示:若 Home 组件实际无需交互(如仅展示静态内容),可考虑改为服务端组件,进一步减少客户端 bundle 体积。
通过这种清晰分层的数据流——服务端获取 → 序列化 → 客户端消费——你既能享受服务端缓存带来的性能优势,又能保留客户端组件所需的交互能力,真正实现「一次获取、多次复用」的现代前端架构目标。










