You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 &quot;Test post&quot;">
<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:

  1. 恢复getStaticProps和getStaticPaths

    • 确保在getStaticProps里获取到post的完整数据,包括body、authorNickname等
    • 把这些数据作为props传给页面组件,这样NextSEO可以在服务端渲染时就生成对应的meta标签
  2. 修复页面组件的NextSEO引用

    • 直接使用props里的postBody、authorNickname、activityId,不需要等到客户端用useQuery获取数据
  3. 调整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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 21:15:35