
在 next.js app router 中,可通过在服务端页面组件中预取数据并作为 props 传入客户端组件,实现服务端缓存 + 客户端交互的合理分工,避免重复请求、提升首屏性能。
在 next.js app router 中,可通过在服务端页面组件中预取数据并作为 props 传入客户端组件,实现服务端缓存 + 客户端交互的合理分工,避免重复请求、提升首屏性能。
在 Next.js 的 App Router 架构下,服务端组件(Server Components)与客户端组件(Client Components)职责明确:服务端组件负责数据获取、渲染逻辑和 SEO 友好内容;客户端组件则专注交互、状态管理与浏览器 API 调用。你无需“嵌套”服务端组件作为子元素来传递数据——这种做法不仅违背设计范式,还会导致 Objects are not valid as a React child 等运行时错误(正如你遇到的 {props: {data}} 被当作 JSX 子节点渲染的问题)。
✅ 正确做法是:直接在 page.js(默认即服务端组件)中执行异步数据获取,并将结果以普通 props 形式传给 use client 标记的客户端组件。Next.js 会自动在服务端完成 fetch 请求、缓存响应(基于 fetch 的默认缓存策略),并将序列化后的数据注入客户端组件的 props 中——整个过程无需手动序列化/反序列化,也无需额外 API 路由。
✅ 推荐实现方式(简洁、高效、符合约定)
app/page.js(服务端页面组件,必须声明为 async)
'use server'; // 可选,但显式标注更清晰(实际 page.js 默认服务端环境)
import Home from './clientComponent';
import { API } from '../utils/api';
export default async function Page() {
const data = await getData();
return <Home data={data} />;
}
async function getData() {
const res = await fetch(API.pages.retrieve(1), {
cache: 'force-cache', // 启用全栈缓存(推荐用于静态内容)
// next: { revalidate: 3600 }, // 可选:每小时重新验证(适用于半动态内容)
});
if (!res.ok) {
throw new Error(`Failed to fetch homepage data: ${res.status}`);
}
return res.json();
}app/clientComponent.js(客户端组件)
'use client';
import { Fragment } from 'react';
import Image from 'next/image';
import Typography from '@mui/material/Typography'; // 假设使用 MUI,可替换为其他 UI 库
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={item.block_title || '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 函数:Next.js 仅在标记为 async 的服务端组件中允许 await;否则会抛出 SyntaxError: Async functions are not supported。
- 禁止在客户端组件中 await 数据:use client 组件运行在浏览器中,无法直接 await fetch()(除非封装在 useEffect + setState 中,但这会失去服务端缓存优势)。
- 数据需可序列化:通过 props 传递的数据必须能被 JSON 序列化(如 Date、Map、Function 等会被丢弃)。若需复杂结构,请在服务端预处理为 plain object/array。
-
缓存策略选择:
- cache: 'force-cache'(默认):复用 CDN/服务器缓存,适合静态内容;
- cache: 'no-store':禁用缓存,每次请求新数据;
- next: { revalidate: N }:服务端每隔 N 秒重新请求并更新缓存(推荐用于新闻、价格等弱时效内容)。
- 错误边界建议:生产环境应在 page.js 或布局中添加 error.js,捕获 getData() 抛出的异常,提供降级 UI。
? 进阶提示:何时真需要 Client Component?
你的 Home 组件当前仅做渲染,理论上完全可作为服务端组件。只有当它包含以下能力时,才需保留 'use client':
- 使用 useState / useEffect / useRef 等 Hook;
- 处理表单提交、按钮点击等用户交互;
- 调用 window, localStorage, navigator 等浏览器专属 API;
- 集成第三方依赖(如图表库、富文本编辑器)要求客户端执行。
若暂无上述需求,可直接移除 'use client',进一步减少客户端 bundle 体积并提升 TTFB。
遵循此模式,你既能享受服务端数据预取与缓存带来的性能红利,又能保持客户端组件的交互灵活性——这才是 Next.js App Router 数据流的最佳实践。










