Next.js App Router中RSC向嵌套子组件传值的方案困惑
Next.js App Router:如何让深层客户端组件获取RSC已获取的数据?
问题场景
我正在将大型Next.js Pages Router项目迁移到App Router,遇到一个棘手的场景:
组件树结构:LandingPage(顶层RSC,page.tsx)-> NavBar(客户端组件)-> UserMenu(客户端组件)-> SignInButton(客户端组件)
LandingPage作为RSC会直接从数据库取数据:
export default async function LandingPage() { const data = await getSiteData(); return ( <> <NavBar /> {/* 其他内容 */} </> ); }
现在最底层的SignInButton需要访问同一个data,目前了解三种方案,但都有顾虑:
- 属性透传:嵌套层级深时体验极差,不想用。
- 客户端组件调用Route Handler:父组件已经取过数据,重复请求冗余,还会丢失TypeScript类型安全。
- Context/状态管理库:误以为会导致LandingPage不能做RSC,觉得不如直接用tRPC放弃RSC。
之前用Pages Router + tRPC时,所有组件都是客户端组件,直接调用tRPC就能自动缓存,不用透传,很方便。现在想找更优的方案,有没有忽略的点?
一、你忽略的核心方案:RSC + 客户端Context Provider(不丢失RSC特性)
你之前的误区是认为用Context就会让LandingPage失去RSC身份——其实完全不会。正确的做法是:
- 写一个客户端组件作为Context Provider,接收RSC传递的data作为props,在内部用Context把数据往下暴露。
- LandingPage(RSC)渲染这个Provider,把获取到的data传进去,然后把需要消费数据的组件放在Provider的children里。
示例代码
1. 创建客户端Context Provider
'use client'; import { createContext, useContext } from 'react'; import type { getSiteData } from '@/lib/data'; type SiteData = Awaited<ReturnType<typeof getSiteData>>; const SiteDataContext = createContext<SiteData | null>(null); export function SiteDataProvider({ children, data }: { children: React.ReactNode; data: SiteData }) { return ( <SiteDataContext.Provider value={data}> {children} </SiteDataContext.Provider> ); } export function useSiteData() { const data = useContext(SiteDataContext); if (!data) throw new Error('useSiteData必须在SiteDataProvider内部使用'); return data; }
2. 修改LandingPage(依然是RSC)
import { SiteDataProvider } from './SiteDataProvider'; export default async function LandingPage() { const data = await getSiteData(); return ( <SiteDataProvider data={data}> <NavBar /> {/* 其他内容 */} </SiteDataProvider> ); }
3. 在深层客户端组件中使用
'use client'; import { useSiteData } from './SiteDataProvider'; export function SignInButton() { const siteData = useSiteData(); return <button>登录 - {siteData.siteName}</button>; }
方案优势
- LandingPage保持RSC特性,依然可以在服务端直接取数据、渲染静态内容。
- 无需属性透传,深层组件直接用
useSiteData获取数据。 - 完全保留TypeScript类型安全,数据从RSC传递到Provider时类型自动同步。
二、重新评估其他现有方案
1. 属性透传:用组件组合优化层级
如果不想用Context,可以通过组件组合减少透传层级:直接在LandingPage里把data传给SignInButton,跳过中间组件的透传。
示例:
// LandingPage(RSC) export default async function LandingPage() { const data = await getSiteData(); return ( <> <NavBar> <UserMenu> <SignInButton siteData={data} /> </UserMenu> </NavBar> {/* 其他内容 */} </> ); }
这种方式通过插槽(children)直接把需要数据的组件传递到上层,比纯属性透传体验好很多。
2. tRPC结合RSC的兼容方案
你不用放弃RSC和tRPC的结合:
- 在RSC里直接调用tRPC的server端方法获取数据,然后把数据传给客户端组件,或者作为初始值传给tRPC的客户端缓存。
- 客户端组件依然可以用tRPC,但通过
initialData参数复用RSC已获取的数据,避免重复请求。
示例:
// LandingPage(RSC) import { api } from '@/trpc/server'; export default async function LandingPage() { const siteData = await api.site.getData.query(); return ( <NavBar initialSiteData={siteData} /> ); }
// NavBar(客户端组件) 'use client'; import { api } from '@/trpc/client'; export function NavBar({ initialSiteData }: { initialSiteData: Awaited<ReturnType<typeof api.site.getData.query>> }) { const { data: siteData } = api.site.getData.useQuery(undefined, { initialData: initialSiteData }); return <UserMenu siteData={siteData} />; }
这样既保留了RSC的服务端渲染能力,又复用了tRPC的缓存机制。
3. 客户端fetch:利用Next.js缓存避免冗余
如果确实需要用客户端fetch,可以利用Next.js的fetch缓存机制,把请求标记为cache: 'force-cache',复用RSC已经缓存的结果,不会重复调用数据库:
'use client'; import type { SiteData } from '@/app/api/site-data/route'; export async function SignInButton() { // 复用RSC已缓存的请求结果 const res = await fetch('/api/site-data', { cache: 'force-cache' }); const data: SiteData = await res.json(); return <button>登录 - {data.siteName}</button>; }
同时通过TypeScript定义API返回类型,保持类型安全。
内容的提问来源于stack exchange,提问作者Haardik
相关产品推荐
相关产品推荐

