Next.js中用Link重入同URL时prefetchQuery不触发SSR数据获取
问题1:SSR场景下使用Next.js的Link组件是否属于不当操作?首屏SSR后后续是不是转为CSR?
完全没问题,Next.js的Link组件就是官方推荐的客户端路由跳转方案。首屏通过SSR渲染后,后续所有通过Link的跳转都会走客户端路由(CSR),不会触发全页面刷新,这是正常且优化的行为,不属于不当操作。
问题2:为何id参数有效时useQuery仍不获取数据?
从你提供的代码来看,主要问题出在缓存匹配和客户端请求触发这两个点上:
手动修改 dehydratedState 的风险
你在getServerSideProps里手动过滤并修改了脱水后的查询缓存,只保留了当前id的单个查询数据。但通过Link跳转时是客户端路由,不会重新执行getServerSideProps预取新数据,此时React Query会先尝试从客户端缓存中读取对应查询键的数据。如果跳转的是新id,缓存里没有匹配的键,就需要发起新请求,但如果请求没触发,大概率是下面的原因:查询键不匹配
检查SUPPORT_QUERY.useGetBoardContent内部的查询键是否和预取时的["support", "faq", id]完全一致。如果查询键生成逻辑不同(比如多了/少了参数、category的拼写不一致),React Query会认为是新查询,要么找不到缓存,要么如果配置了enabled: false就不会发起请求。客户端请求未触发
默认情况下useQuery会自动发起请求,但如果你的自定义hook里设置了enabled条件(比如依赖某个状态),或者请求本身有错误被静默处理了,都会导致数据一直是undefined。可以在浏览器DevTools的Network面板查看是否有对应的API请求发出,或者在useGetBoardContent里添加错误日志排查。数据结构修改导致的缓存问题
你在脱水时把targetQueryState.data.data赋值给了state.data,这会改变缓存中数据的结构。如果客户端useQuery期望的是原始接口返回的结构,可能会导致解析错误,进而无法正确渲染数据。建议尽量不要手动修改脱水后的查询数据结构,保持预取和客户端查询的数据结构一致。
修复建议
- 移除
getServerSideProps中手动修改dehydratedState.queries的逻辑,直接返回dehydrate(queryClient)生成的完整脱水状态,避免破坏缓存结构。 - 确保
useGetBoardContent的查询键和预取时的["support", category, id]完全匹配(category要正确传入)。 - 在客户端添加错误处理,比如通过
useQuery的error属性排查请求失败原因。
相关代码参考
页面组件(FAQDetailPage)
// PAGE, I pass the query string to the <FAQContent /> Component export default function FAQDetailPage({ id }: { id: string }) { return <FAQContent id={id} category="faq" />; } export async function getServerSideProps({ query: { id }, }: { query: { id: string } }) { const queryClient = new QueryClient(); try { await queryClient.prefetchQuery(["support", "faq", id], () => { return sendRequest({ method: "get", path: `/article/${id}`, apiType: "Contents", }); }); } catch (e) { return { redirect: { destination: "/", permanent: false, }, }; } // 直接返回完整的脱水状态,不要手动修改 return { props: { dehydratedState: dehydrate(queryClient), id, }, }; }
FAQContent组件
// Get id and category from the page, and pass to <SupportBoardContent /> export default function FAQContent({ id, category, }: { id: string; category: string }) { return <SupportBoardContent id={id} category={category} />; }
SupportBoardContent组件
// Here is the component where hydrate the dehydrated query export default function SupportBoardContent({ id, category, }: { id: string; category: string }) { const router = useRouter(); const { pathname } = router; // This is a hook for useQuery. const { data: boardContent, error, ResponseHandler: ResponseHandlerOfGetBoardContent, } = SUPPORT_QUERY.useGetBoardContent({ id, category, }); // 添加错误日志排查 if (error) console.error("Query error:", error); const { title, contents, memo, videoURL, articleType } = boardContent?.targetArticle || {}; return ( <SupportLayout> {/* 可以添加加载状态提示 */} {!boardContent && !error && <div>加载中...</div>} {boardContent && ( <CommonStyled.mainContainer> <SupportBreadcrumb title={title} /> <BoardPageTitle titleStyle="default"> {getArticleBoardTypeString(articleType)} </BoardPageTitle> <BoardContent title={title} annotation={memo} desc={contents} videoURL={videoURL} /> </CommonStyled.mainContainer> )} {error && <div>加载失败,请重试</div>} </SupportLayout> ); }
内容的提问来源于stack exchange,提问作者carryon

