Next.js+StepZen GraphQL实现Reddit克隆:帖子详情页无法获取数据及变量未提供错误求助
问题分析与解决方案
先看你遇到的错误Error! Variable '$title' of required type 'String!' was not provided.,根源是变量传递不匹配,再加上Next.js路由参数的加载时机问题,还有查询返回类型的不匹配,咱们一步步解决:
1. 修正变量传递的核心问题
你的GraphQL查询需要的是$title变量,但在useQuery里你传的是id: router.query.id,这完全不对应!
解决步骤:
- 首先确认你的路由文件名称:如果是通过slug(比如帖子标题的格式化版本)跳转,你的路由应该是
pages/posts/[title].tsx(或者jsx),这时候router.query.title才是你要传的变量值。 - 修改
useQuery的变量部分:
const { data, error, loading } = useQuery(GET_POST_LIST_BY_TITLE, { variables: { title: router.query.title as string }, // 把id改成title,对应查询的$title变量 skip: !router.query.title, // 当title不存在时跳过查询,避免触发错误 });
2. 处理Next.js路由参数的加载时机
在Next.js客户端渲染中,router.query初始是undefined,如果直接执行useQuery,会在参数还没准备好的时候发送请求,导致变量缺失。
优化方案:
- 添加加载状态判断,同时用
skip选项避免提前执行查询:
const PostPage = () => { const router = useRouter(); const { data, error, loading } = useQuery(GET_POST_LIST_BY_TITLE, { variables: { title: router.query.title as string }, skip: !router.query.title, // 没有title时不执行查询 }); // 路由参数还没加载完成时显示加载状态 if (!router.isReady || loading) { return <div>Loading...</div>; } if (error) { return <div>Error! {error.message}</div>; } // 注意:查询返回的是数组,取第一个元素 const post = data?.getPostListByPostTitle?.[0]; if (!post) { return <div>Post not found!</div>; } return ( <div> <Post post={post} /> </div> ); };
3. 修正查询返回类型的不匹配
你的StepZen Schema里getPostListByPostTitle返回的是[Post]数组,但你在组件里直接把它赋值给单个Post类型的变量,这会导致类型错误和数据取不到的问题。
两种优化方式:
方式一:修改StepZen Schema返回单个Post(推荐,因为标题应该是唯一的)
// 把getPostListByPostTitle改成getPostByTitle,返回单个Post getPostByTitle(title: String!): Post @dbquery( type: "postgresql" query: """ select * from "post" where title = $1 limit 1 """ configuration: "postgresql_config" )
然后同步修改你的GraphQL查询语句:
export const GET_POST_BY_TITLE = gql` query GetPostByTitle($title: String!) { getPostByTitle(title: $title) { id title username body created_at image subreddit { created_at id topic } subreddit_id comments { created_at id text } votes { created_at upvote username post_id id } } } `;
组件里就可以直接取const post = data?.getPostByTitle;,不用再取数组元素了。
方式二:保持数组返回,但组件里正确处理
如果不想改Schema,就在组件里取数组的第一个元素:
const post = data?.getPostListByPostTitle?.[0]; // 还要判断post是否存在,避免渲染空数据 if (!post) { return <div>Post not found!</div>; }
4. 可选:用SSR预取数据(解决客户端加载的延迟问题)
如果想用服务端渲染(SSR)来提前获取数据,可以使用getServerSideProps:
import { gql, ApolloClient, InMemoryCache } from "@apollo/client"; export async function getServerSideProps(context) { const { title } = context.params; if (!title) { return { notFound: true, }; } // 创建临时Apollo客户端 const client = new ApolloClient({ uri: "你的StepZen GraphQL端点", cache: new InMemoryCache(), }); try { const { data } = await client.query({ query: GET_POST_BY_TITLE, // 用修改后的单个Post查询 variables: { title }, }); return { props: { post: data.getPostByTitle, }, }; } catch (error) { return { props: { error: error.message, }, }; } } const PostPage = ({ post, error }) => { if (error) { return <div>Error! {error}</div>; } if (!post) { return <div>Post not found!</div>; } return ( <div> <Post post={post} /> </div> ); }; export default PostPage;
最后检查点
- 确认你的StepZen端点配置正确,数据库里确实存在对应title的帖子。
- 确认路由参数传递正确:比如跳转时用
router.push(/posts/${encodeURIComponent(post.title)}),避免特殊字符导致参数解析错误。
内容的提问来源于stack exchange,提问作者Aliyasir Naç
相关产品推荐
相关产品推荐

