Next.js 13 App目录下服务端获取路由跳转后searchParams问题
问题
我正在开发一个基于Next.js 13 App目录的多语言应用,设有/products页面,需要通过URLSearchParams过滤页面数据。我单独创建了客户端过滤组件,通过如下方式跳转路由:http://localhost:3001/en/products?accessories=led-light--switch。我希望在服务端文件page.tsx中获取查询参数,根据文档可访问searchParams,但路由跳转后searchParams为空,仅刷新页面后才正常显示。如何在路由跳转后从服务端获取查询参数?
Filter组件代码
const onChangeHandler = (e: React.ChangeEvent) => { const { name, value, checked } = e.target; const newValues = checked ? [...checkBoxValues, value] : checkBoxValues.filter((val) => val !== value); setCheckBoxValues(newValues); if (newValues.length === 0) { params.delete(name); // 从params中移除整个'jobType'键 } else { params.set(name, newValues.join("--")); // 为'jobType'键设置新值 } router.push(pathname + (params.toString() ? "?" + params.toString() : "")); };
page.tsx文件代码
export default async function IndexPage({ params: { lang }, searchParams, }: { params: { lang: Locale }; searchParams: any; }) { const data = await getData(variables.apiUrls.product); const dictionary = await getDictionary(lang); let customMeta = { ...data.data.meta, title_en: data.data.meta.title_en.length > 0 ? data.data.meta.title_en : translateText(lang, data.data), title_bn: data.data.meta.title_bn.length > 0 ? data.data.meta.title_bn : translateText(lang, data.data), }; return ( <BaseLayout navigationType="others" lang={lang} meta={customMeta}> <ProductIndex lang={lang} dictionary={dictionary} /> </BaseLayout> ); }
解决方案
问题核心是Next.js 13 App Router的客户端软导航默认不会触发服务端组件重新渲染,导致searchParams未更新,只有硬刷新才会重新拉取服务端数据。以下是三种可行解决方式:
方式一:强制触发服务端渲染
在router.push时添加配置,强制服务端组件重新渲染以获取最新查询参数:
router.push( pathname + (params.toString() ? "?" + params.toString() : ""), undefined, { forceOptimisticNavigation: true } );
方式二:客户端处理过滤逻辑
既然过滤组件是客户端组件,可将数据过滤逻辑迁移到客户端:
- 服务端组件
page.tsx获取全量产品数据,传递给客户端组件ProductIndex - 在
ProductIndex中用useSearchParams钩子获取实时查询参数,在客户端完成过滤
修改page.tsx
export default async function IndexPage({ params: { lang }, searchParams, }: { params: { lang: Locale }; searchParams: any; }) { const data = await getData(variables.apiUrls.product); const dictionary = await getDictionary(lang); let customMeta = { ...data.data.meta, title_en: data.data.meta.title_en.length > 0 ? data.data.meta.title_en : translateText(lang, data.data), title_bn: data.data.meta.title_bn.length > 0 ? data.data.meta.title_bn : translateText(lang, data.data), }; return ( <BaseLayout navigationType="others" lang={lang} meta={customMeta}> <ProductIndex lang={lang} dictionary={dictionary} products={data.data.products} /> </BaseLayout> ); }
修改ProductIndex客户端组件
'use client'; import { useSearchParams } from 'next/navigation'; export default function ProductIndex({ lang, dictionary, products }) { const searchParams = useSearchParams(); // 根据查询参数过滤产品 const filteredProducts = products.filter(product => { const accessories = searchParams.get('accessories'); if (!accessories) return true; const accessoryList = accessories.split('--'); return accessoryList.some(acc => product.accessories.includes(acc)); }); return ( <div> {filteredProducts.map(product => ( <div key={product.id}>{product.name}</div> ))} </div> ); }
方式三:触发服务端路径重新验证
在客户端跳转后调用revalidatePath,强制服务端重新获取最新参数并渲染:
'use client'; import { useRouter, usePathname } from 'next/navigation'; import { revalidatePath } from 'next/cache'; const onChangeHandler = (e: React.ChangeEvent) => { const { name, value, checked } = e.target; const newValues = checked ? [...checkBoxValues, value] : checkBoxValues.filter((val) => val !== value); setCheckBoxValues(newValues); if (newValues.length === 0) { params.delete(name); } else { params.set(name, newValues.join("--")); } const newPath = pathname + (params.toString() ? "?" + params.toString() : ""); router.push(newPath); revalidatePath(newPath); };
内容的提问来源于stack exchange,提问作者bishal dores
相关产品推荐
相关产品推荐

