如何在next-mdx-remote的MDX组件中使用react-query?
解决next-mdx-remote中react-query找不到QueryClient的问题
这个问题的核心其实和next-mdx-remote的渲染机制有关——你用的renderToString是同步的服务端渲染方法,它执行的时候完全脱离了_app.js里QueryClientProvider的上下文环境。
简单说:你传入components里的那些组件,在renderToString执行时是被直接编译成字符串的,并没有被包裹在你全局配置的QueryClientProvider里,所以组件里的useQuery自然找不到对应的QueryClient实例。
下面给你几个可行的解决方案,按推荐程度排序:
方案1:页面层预取数据,通过props传给MDX组件(最推荐)
把数据请求逻辑从MDX组件里抽出来,放到页面的getStaticProps/getServerSideProps中,用react-query预取数据后,通过props传递给MDX组件。这样MDX组件只负责展示,完全避开上下文问题,还能利用Next.js的预渲染优化性能。
示例代码:
// 页面组件 import { dehydrate, QueryClient } from '@tanstack/react-query'; import { renderToString } from 'next-mdx-remote/render-to-string'; import { MDXRemote } from 'next-mdx-remote'; // 你的MDX组件,现在只接收props展示数据 const DataDisplayComponent = ({ data }) => { return <div>{JSON.stringify(data)}</div>; }; export async function getStaticProps() { // 1. 创建临时QueryClient预取数据 const queryClient = new QueryClient(); await queryClient.prefetchQuery({ queryKey: ['your-target-data'], queryFn: () => fetch('https://api.example.com/data').then(res => res.json()), }); // 2. 渲染MDX内容,给组件预留props位 const mdxContent = `# 这是MDX内容 <DataDisplayComponent />`; const mdxSource = await renderToString(mdxContent, { components: { DataDisplayComponent }, }); // 3. 返回预取的状态和MDX源 return { props: { mdxSource, dehydratedState: dehydrate(queryClient), }, }; } export default function MdxPage({ mdxSource, dehydratedState }) { // 从react-query的缓存中取出数据,传给MDX组件 const data = useQuery(['your-target-data']).data; return ( <MDXRemote {...mdxSource} components={{ DataDisplayComponent: () => <DataDisplayComponent data={data} /> }} /> ); }
方案2:给MDX组件手动包裹QueryClientProvider
如果必须在MDX组件里用useQuery,可以在renderToString的时候,给需要使用react-query的组件手动套一层QueryClientProvider,同时要记得同步服务端和客户端的缓存状态:
// 页面的getStaticProps中 import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { dehydrate } from '@tanstack/react-query'; import { renderToString } from 'next-mdx-remote/render-to-string'; export async function getStaticProps() { const queryClient = new QueryClient(); // 渲染MDX时,给目标组件包裹QueryClientProvider const mdxSource = await renderToString(yourMdxContent, { components: { YourQueryComponent: (props) => ( <QueryClientProvider client={queryClient}> <YourQueryComponent {...props} /> </QueryClientProvider> ), }, }); return { props: { mdxSource, dehydratedState: dehydrate(queryClient), }, }; }
这种方式要注意:服务端创建的QueryClient状态必须通过dehydrate传递给客户端,否则客户端会重新请求一次数据,造成冗余请求。
额外注意事项
- 如果你的
useQuery的queryFn涉及浏览器专属API(比如window),一定要加判断,避免服务端渲染时报错:useQuery({ queryKey: ['data'], queryFn: () => fetchData(), enabled: typeof window !== 'undefined', // 只在客户端执行请求 }); - 确保
_app.js里的Hydrate组件正确包裹了所有页面,这样客户端才能复用服务端预取的缓存数据。
内容的提问来源于stack exchange,提问作者antonwilhelm
相关产品推荐
相关产品推荐

