NextSEO配置OpenGraph在特定页面失效问题求助
NextSEO OpenGraph 在生产页面失效问题
我已经调试了好几个小时,用NextSEO配置的OpenGraph在其他页面正常,但这个页面就是不行。
一开始我尝试通过getStaticProps加载变量传给主组件,代码如下:
return ( <> <NextSeo title={`${authorNickname} on Breakout "${postBody}"`} description={postBody} openGraph={{ title: `${authorNickname} on Breakout "${postBody}"`, description: postBody, url: `https://breakout.so/post/${authorNickname}/${activityId}`, site_name: "breakout.so", }} /> // ... 其他主组件 </>);
用Chrome的localhost open graph checker插件测试时,meta数据显示完全正确:
<meta name="viewport" content="width=device-width"> <meta charset="utf-8"> <meta name="msapplication-TileColor" content="#da532c"> <meta name="theme-color" content="#ffffff"> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"> <link rel="manifest" href="/site.webmanifest"> <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5"> <meta name="robots" content="index,follow"> <meta name="description" content="Test post"> <meta property="og:title" content="eric on Breakout "Test post""> <meta property="og:description" content="Test post"> <meta property="og:url" content="https://breakout.so/post/eric/0f35c9f0-65b6-11ed-8080-80010701c78a"> <meta property="og:site_name" content="breakout.so"> <meta name="next-head-count" content="15"><noscript data-n-css=""></noscript> <script src="https://cdn.iubenda.com/iubenda.js"></script> <script defer="" nomodule="" src="/_next/static/chunks/polyfills.js?ts=1668606808077"></script> <script src="/_next/static/chunks/webpack.js?ts=1668606808077" defer=""></script> <script src="/_next/static/chunks/main.js?ts=1668606808077" defer=""></script> <script src="/_next/static/chunks/pages/_app.js?ts=1668606808077" defer=""></script> <script src="/_next/static/chunks/pages/community.js?ts=1668606808077" defer=""></script> <script src="/_next/static/development/_buildManifest.js?ts=1668606808077" defer=""></script> <script src="/_next/static/development/_ssgManifest.js?ts=1668606808077" defer=""></script> <script src="/_next/static/development/_middlewareManifest.js?ts=1668606808077" defer=""></script> <style>
甚至用OpenGraph检测工具也能正确解析,但访问实际生产URL时,页面会默认用_app.js里的标题和描述;删掉_app.js里的配置后,就完全解析不到任何OpenGraph数据了。
下面是我当前的代码(部分细节省略,注释掉的函数头和getStaticPaths/Props也没能解决问题):
// export default function PostDetails({ postBody, activityId, authorNickname }) { export default function PostDetails({}) { const router = useRouter(); const { activityId, authorNickname } = router.query; const [isRoutineSnapshotOpen, setIsRoutineSnapshotOpen] = useState(true); const [result] = useQuery({ query: gql` query ($nickname: String, $postId: ID) { # query ($nickname: String) { user(nickname: $nickname) { id } postDetails(id: $postId) { body routineSnapshot } } `, variables: { nickname: authorNickname, postId: activityId }, // variables: { nickname: authorNickname }, pause: !router.isReady, }); const { data, fetching, error } = result; if (!data || fetching) { return "Loading post details..."; } if (error) { return <p>Unexpected error occurred. Try refreshing the page.</p>; } if (!data?.user) { return <p>Couldn't find user {authorNickname}</p>; } const authorId = data.user.id; return ( <> <NextSeo title={`${authorNickname} on Breakout ${postDetails?.body ? `"${postDetails.body}"` : ""}`} description={postDetails?.body || ""} openGraph={{ title: `${authorNickname} on Breakout ${postDetails?.body ? `"${postDetails.body}"` : ""}`, description: postDetails?.body || "", url: `https://breakout.so/post/${authorNickname}/${activityId}`, site_name: "breakout.so", }} /> <div className="bg-white px-4 border-none py-2 sm:px-6"> <SinglePost activityId={activityId} feedGroup="user" userId={authorId} Activity={(props) => { return useMemo(() => { return ( <Activity {...props} Header={({ activity }) => ( <ActivityHeader activity={activity} /> )} Content={({ activity }) => ( <> <ActivityContent activity={activity} /> <div className="">{userInfoAndRoutineSection}</div> </> )} Footer={({ activity }) => ( <div> <div className="w-full border-t border-gray-300"></div> <CommentInputField activity={activity} /> <CommentList activityId={activity.id} CommentItem={(props) => { const createLinkToProfile = (children) => ( <Link href={{ pathname: "/u/[nickname]", query: { nickname: props.comment.user.data.nickname, }, }} > {children} </Link> ); const taggedUsers = (props.comment.data?.taggedUsers || []).map((nickname) => `@${nickname}`); const textArray = props.comment.data.text.split(" "); const textWithHighlights = textArray .map((word) => { if (taggedUsers.includes(word)) { return `<span class="text-green-700">${word}</span>`; } return word; }) .join(" "); const text = ( <div dangerouslySetInnerHTML={{ __html: `<p>${textWithHighlights}</p>`, }} /> ); return ( <div className="my-2 flex space-x-3"> <div className="shrink-0"> {createLinkToProfile( <img className="h-10 w-10 rounded-full cursor-pointer" src={props.comment.user.data.image} alt="" /> )} </div> <div className="text-sm"> <div className="p-2 bg-gray-100 rounded-md"> {createLinkToProfile( <a className="text-gray-900 font-semibold hover:text-violet-600"> {props.comment.user.data.nickname} </a> )} <div className="text-gray-700">{text}</div> </div> <div> <span className="ml-2 text-gray-500"> {formatTimeAgoUtc(props.comment.created_at)} </span> {/* <button type="button" className="text-gray-500 hover:text-gray-900" onClick={() => {}} > Reply </button> */} </div> </div> </div> ); }} /> </div> )} /> ); // [props] or [props.activity] keeps re-rendering it... doesn't really seem to matter so just passing id }, [props.activity.id]); }} /> </div> </> ); } // export async function getStaticPaths() { // const posts = await prisma.post.findMany({ // include: { // user: true, // }, // }); // // Get the paths we want to pre-render based on posts // const paths = posts // .filter((post) => post.body) // .map((post) => ({ // params: { authorNickname: post.user.nickname, activityId: post.id }, // })); // // We'll pre-render only these paths at build time. // // { fallback: false } means other routes should 404. // return { paths, fallback: true }; // } // export async function getStaticProps({ params }) { // if (!params.authorNickname || !params.activityId) { // return { props: {} }; // } // const post = await prisma.post.findUnique({ // where: { id: params.activityId }, // include: { // review: true, // routineSnapshot: true, // }, // }); // return { // props: { // urqlState: ssrCache.extractData(), // postBody: post?.body || post?.review.comment || "", // routineSnapshot: post.routineSnapshot, // activityId: params.activityId, // authorNickname: params.authorNickname, // }, // revalidate: 30, // }; // }
问题原因及解决方案
核心原因
你现在的代码是客户端渲染时才获取数据并渲染NextSEO,但社交平台和搜索引擎的爬虫大多只会抓取页面初始的HTML内容,不会等待客户端JS执行。而你注释掉了getStaticProps,导致页面初始HTML里没有NextSEO生成的meta标签,只能 fallback 到_app.js的配置,删掉_app.js的配置后就没有任何meta了。
解决方案
恢复并正确配置getStaticProps和getStaticPaths,确保页面在构建时(或ISR时)就生成包含正确OpenGraph标签的HTML:
恢复
getStaticProps和getStaticPaths- 确保在
getStaticProps里获取到post的完整数据,包括body、authorNickname等 - 把这些数据作为props传给页面组件,这样NextSEO可以在服务端渲染时就生成对应的meta标签
- 确保在
修复页面组件的NextSEO引用
- 直接使用props里的
postBody、authorNickname、activityId,不需要等到客户端用useQuery获取数据
- 直接使用props里的
调整
fallback配置(可选)- 如果你的帖子数量不多,可以设置
fallback: false,构建时预渲染所有页面 - 如果帖子数量多,用
fallback: 'blocking',让未预渲染的页面在首次访问时服务端渲染,确保爬虫能拿到完整meta标签
- 如果你的帖子数量不多,可以设置
修改后的关键代码示例:
export default function PostDetails({ postBody, activityId, authorNickname }) { // 直接使用props数据,无需客户端请求 return ( <> <NextSeo title={`${authorNickname} on Breakout "${postBody}"`} description={postBody} openGraph={{ title: `${authorNickname} on Breakout "${postBody}"`, description: postBody, url: `https://breakout.so/post/${authorNickname}/${activityId}`, site_name: "breakout.so", }} /> // ... 其他组件渲染逻辑 </> ); } export async function getStaticPaths() { const posts = await prisma.post.findMany({ include: { user: true }, }); const paths = posts.map((post) => ({ params: { authorNickname: post.user.nickname, activityId: post.id }, })); return { paths, fallback: 'blocking' }; // 用blocking确保未预渲染页面也能SSR } export async function getStaticProps({ params }) { if (!params.authorNickname || !params.activityId) { return { notFound: true }; } const post = await prisma.post.findUnique({ where: { id: params.activityId }, include: { user: true, review: true }, }); if (!post) { return { notFound: true }; } const postBody = post?.body || post?.review?.comment || ""; return { props: { postBody, activityId: params.activityId, authorNickname: params.authorNickname, }, revalidate: 30, // 开启ISR,30秒重新生成页面 }; }
额外注意事项
- 确保生产环境的Next.js应用是静态导出或使用SSR/ISR模式,不要纯客户端渲染
- 测试生产页面时,用爬虫模拟工具查看初始HTML,而不是浏览器开发者工具(浏览器会执行JS后的DOM)
- 如果
_app.js里有全局的NextSEO配置,确保页面级的NextSEO能覆盖全局配置(NextSEO默认支持优先级:页面级 > 全局级)
内容的提问来源于stack exchange,提问作者Eric
相关产品推荐
相关产品推荐

