Next.js App Router嵌套布局中动态获取页面UID实现数据过滤
问题背景
我在Next.js App Router中用catchall路由构建了如下嵌套动态页面结构:
app/stay/ |__[...category] | |__page.js |__[uid] | |__page.js |__layout.js
在layout.js中配置了Context Provider,用来存储过滤后的数据并包裹子页面:
export default async function StayLayout({ children }) { const client = createClient(); const stay_pages = await client.getAllByType('stay_page', { fetchLinks: ["location_category.category_title", "stay_category.category_title"], orderings: [ { field: "my.stay_page.listing_title", direction: "asc", }, ], }, ) const filteredStayListings = Array.from( new Set(stay_pages.filter((item) => item.data.stay_category.uid === 'guest-ranches-lodges')) ) return ( <StayProvider filteredStayListings={filteredStayListings} > {children} </StayProvider> ) }
当前生成的路径类似/stay/guest-ranches-lodges/lodge-name,需要把过滤条件里硬编码的'guest-ranches-lodges'替换为页面的动态UID,但父布局无法获取子页面的UID或路径参数,且Next.js文档明确:
无法在父布局与子页面之间传递数据。不过,你可以在路由中多次获取同一数据,React会自动去重,不会影响性能。
可行解决方案
方案1:将Context Provider下移到子页面
既然父布局拿不到子页面的参数,直接把Provider移到[...category]/page.js和[uid]/page.js中,在子页面里获取动态参数后过滤数据,再传递给Provider。
以[...category]/page.js为例:
import { StayProvider } from '@/components/StayProvider'; import createClient from '@/lib/client'; export default async function CategoryPage({ params }) { // 从catchall参数中提取目标UID,比如params.category[0]就是guest-ranches-lodges const targetUid = params.category[0]; // 复用原布局中的数据获取逻辑 const client = createClient(); const stay_pages = await client.getAllByType('stay_page', { fetchLinks: ["location_category.category_title", "stay_category.category_title"], orderings: [ { field: "my.stay_page.listing_title", direction: "asc", }, ], }, ); // 根据动态UID过滤数据 const filteredStayListings = stay_pages.filter(item => item.data.stay_category.uid === targetUid ); return ( <StayProvider filteredStayListings={filteredStayListings}> {/* 页面内容 */} <div>...</div> </StayProvider> ); }
[uid]/page.js同理,直接从params.uid拿到目标值后过滤数据即可。
方案2:用数据缓存库实现复用与过滤
如果多个页面需要复用这份数据,可以用React Query或SWR做缓存,避免重复请求的同时,在需要的地方根据参数过滤:
- 先在工具层封装数据获取函数:
// lib/stays.js import createClient from './client'; export async function getAllStayPages() { const client = createClient(); return client.getAllByType('stay_page', { fetchLinks: ["location_category.category_title", "stay_category.category_title"], orderings: [ { field: "my.stay_page.listing_title", direction: "asc", }, ], }, ); }
- 在子页面中用React Query获取并过滤:
'use client'; import { useQuery } from '@tanstack/react-query'; import { getAllStayPages } from '@/lib/stays'; import { StayProvider } from '@/components/StayProvider'; export default function CategoryPage({ params }) { const targetUid = params.category[0]; const { data: stay_pages } = useQuery({ queryKey: ['stayPages'], queryFn: getAllStayPages, }); const filteredStayListings = stay_pages?.filter(item => item.data.stay_category.uid === targetUid ) || []; return ( <StayProvider filteredStayListings={filteredStayListings}> {/* 页面内容 */} </StayProvider> ); }
这种方式下,即使多个页面调用数据,缓存库会自动复用请求结果,符合Next.js文档中关于重复获取数据的性能提示。
方案3:调整路由结构让布局能拿到参数
如果允许调整路由,可以把动态参数提升到布局所在层级,比如:
app/stay/[categoryUid]/ |__[...subpath] | |__page.js |__layout.js
这样stay/[categoryUid]/layout.js就能直接拿到params.categoryUid,在布局里完成过滤并提供Context:
export default async function StayCategoryLayout({ children, params }) { const client = createClient(); const stay_pages = await client.getAllByType('stay_page', { fetchLinks: ["location_category.category_title", "stay_category.category_title"], orderings: [ { field: "my.stay_page.listing_title", direction: "asc", }, ], }, ); const filteredStayListings = stay_pages.filter(item => item.data.stay_category.uid === params.categoryUid ); return ( <StayProvider filteredStayListings={filteredStayListings}> {children} </StayProvider> ); }
总结
- 方案1简单直接,无额外依赖,适合数据仅在当前子页面使用的场景;
- 方案2适合多页面复用数据的场景,通过缓存减少请求开销;
- 方案3需要调整路由结构,适合路由逻辑允许的情况。
内容的提问来源于stack exchange,提问作者J. Gunderson

