Facebook 调试器未拾取 Next.js next-seo 元标记

nus*_*ara 10 javascript seo reactjs server-side-rendering next.js

感觉这个问题已经被问了一百遍了。所以,这可能是一个很难回答的问题。我将为我的案例提供更多细节。也许会有帮助。

\n

我正在使用 Next.js ^10.0.9 和next-seo ^4.22.0。我可以在 devtools 中看到元标记,但 FB 和 Twitter 以及许多其他元标记验证器无法获取它们。

\n

从这里和其他地方的其他几个问题来看,似乎存在一些共识,只要它不在源代码中,即,如果我们“检查源代码”并且看不到它,那么它就不可用到刮刀。据我了解,这是因为这些机器人不运行渲染元标记所需的 JavaScript。

\n

来源

\n

此页面源应该包含用于描述、标题、图像和其他一些内容的 opengraph 元标记,但它没有:

\n
<!DOCTYPE html>\n<html lang="en">\n    <head>\n        <style data-next-hide-fouc="true">\n        body{display:none}\n    </style>\n    <noscript data-next-hide-fouc="true">\n        <style>\n        body{display:block}\n    </style>\n</noscript>\n\n<meta charSet="utf-8"/>\n<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width"/>\n<meta name="next-head-count" content="2"/>\n\n<noscript data-n-css=""></noscript>\n\n<link rel="preload" href="/_next/static/chunks/main.js?ts=1616232654196" as="script"/>\n<link rel="preload" href="/_next/static/chunks/webpack.js?ts=1616232654196" as="script"/>\n<link rel="preload" href="/_next/static/chunks/pages/_app.js?ts=1616232654196" as="script"/>\n<link rel="preload" href="/_next/static/chunks/pages/index.js?ts=1616232654196" as="script"/>\n\n<noscript id="__next_css__DO_NOT_USE__"></noscript>\n\n<style id="jss-server-side">html {\n  box-sizing: border-box;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n*, *::before, *::after {\n  box-sizing: inherit;\n}\nstrong, b {\n  font-weight: 700;\n}\nbody {\n  color: rgba(0, 0, 0, 0.87);\n  margin: 0;\n  font-size: 0.875rem;\n  font-family: "Roboto", "Helvetica", "Arial", sans-serif;\n  font-weight: 400;\n  line-height: 1.43;\n  letter-spacing: 0.01071em;\n  background-color: #c3d0f5;\n}\n@media print {\n  body {\n    background-color: #fff;\n  }\n}\nbody::backdrop {\n  background-color: #c3d0f5;\n}</style>\n\n</head>\n\n<body>\n    <div id="__next">\n        <div class="Loading__Center-sc-9gpo7v-0 dctQei">\n            <div style="width:100%;height:100%;overflow:hidden;margin:0 auto;outline:none" title="" role="button" aria-label="animation" tabindex="0">\n            </div>\n            <h2>This is you. This is how you wait.</h2>\n        </div>\n    </div>\n    \n    <script src="/_next/static/chunks/react-refresh.js?ts=1616232654196"></script>\n    \n    <script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/","query":{},"buildId":"development","runtimeConfig":{"APP_NAME":"example","APP_DESCRIPTION":"Discover examples of examples of examples.","APP_URL":"https://example.com","nextExport":true,"autoExport":true,"isFallback":false}</script>\n    \n    <script nomodule="" src="/_next/static/chunks/polyfills.js?ts=1616232654196"></script>\n    <script src="/_next/static/chunks/main.js?ts=1616232654196"></script>\n    <script src="/_next/static/chunks/webpack.js?ts=1616232654196"></script>\n    <script src="/_next/static/chunks/pages/_app.js?ts=1616232654196"></script>\n    <script src="/_next/static/chunks/pages/index.js?ts=1616232654196"></script>\n    <script src="/_next/static/development/_buildManifest.js?ts=1616232654196"></script>\n    <script src="/_next/static/development/_ssgManifest.js?ts=1616232654196"></script>\n</body>\n</html>\n
Run Code Online (Sandbox Code Playgroud)\n

构建时生成的页面?

\n

让我困惑的是 Next.js 没有渲染它们。根据文档

\n
\n

默认情况下,Next.js 预渲染每个页面。这意味着 Next.js\n提前为每个页面生成 HTML,而不是\n由客户端 JavaScript 完成这一切。预渲染可以带来更好的\n性能和 SEO。

\n
\n

除非我误读了这一点,否则这意味着只要我使用默认设置,页面就会通过 SSR 或静态生成来呈现。正确的?

\n

此外,上面的来源提到"nextExport":true,"autoExport":true,我认为这表明该页面应该在构建时创建。

\n

我的代码可能经历了一些更改,但我很确定它们没有偏离 SSR 或静态生成。

\n

代码的高级视图

\n

在某个时候,我添加了 _document.tsx 和 _app.tsx。

\n

我没有对 _document.tsx 进行太多更改。Head经过一些实验,我认为从这里开始是至关重要的next/document

\n
import Document, { Head, Html, Main, NextScript } from "next/document";\n\nimport React from "react";\nimport { ServerStyleSheets } from "@material-ui/core/styles";\nimport theme from "../styles/theme";\n\nexport default class MyDocument extends Document {\n  render() {\n    return (\n      <Html lang="en">\n        <Head />\n        <body>\n          <Main />\n          <NextScript />\n        </body>\n      </Html>\n    );\n  }\n}\n\nMyDocument.getInitialProps = async (ctx) => {\n  const sheets = new ServerStyleSheets();\n  const originalRenderPage = ctx.renderPage;\n\n  ctx.renderPage = () =>\n    originalRenderPage({\n      enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),\n    });\n\n  const initialProps = await Document.getInitialProps(ctx);\n\n  return {\n    ...initialProps,\n    styles: [\n      ...React.Children.toArray(initialProps.styles),\n      sheets.getStyleElement(),\n    ],\n  };\n};\n
Run Code Online (Sandbox Code Playgroud)\n

_app.tsx 有更多更改,但我认为不会影响元标记。如果我在这里手动放置元标记Head,它们会显示在源代码中。对于默认设置效果很好,但我希望能够以编程方式为每个页面执行此操作。

\n
import { useEffect, useState } from "react";\n\nimport { AppProps } from "next/app";\nimport CssBaseline from "@material-ui/core/CssBaseline";\nimport Layout from "../components/Layout";\nimport { ThemeProvider } from "@material-ui/core/styles";\nimport { User } from "../context/user";\nimport theme from "../styles/theme";\n\nexport default function App({ Component, pageProps }: AppProps) {\n  const [user, setUser] = useState({\n    auth: null,\n    loading: true,\n  });\n\n  useEffect(() => {\n    const getUser = async () => {\n      const res = await fetch("/api/auth/me");\n      const auth = res.ok ? await res.json() : null;\n      setUser({ auth, loading: false });\n    };\n    getUser();\n\n    const jssStyles = document.querySelector("#jss-server-side");\n    if (jssStyles) {\n      jssStyles &&\n        jssStyles.parentElement &&\n        jssStyles.parentElement.removeChild(jssStyles);\n    }\n  }, []);\n\n  return (\n    <>\n      <User.Provider value={{ user, setUser }}>\n        <ThemeProvider theme={theme}>\n          <CssBaseline />\n          <Layout>\n            <Component {...pageProps} />\n          </Layout>\n        </ThemeProvider>\n      </User.Provider>\n    </>\n  );\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Head使用 next-seo 创建了一个组件。它做了相当多的工作。也许只需注意它直接返回NextSeo,而不将其放入 React 片段或其他组件中:

\n
import { NextPage } from "next";\nimport { NextSeo } from "next-seo";\nimport React from "react";\nimport getConfig from "next/config";\nconst { publicRuntimeConfig } = getConfig();\ninterface Props {\n  page?: string;\n  description?: string;\n  image?: {\n    url: string;\n    alt: string;\n  };\n}\n\nconst appDescription = publicRuntimeConfig.APP_DESCRIPTION;\nconst appName = publicRuntimeConfig.APP_NAME;\nconst appUrl = publicRuntimeConfig.APP_URL;\nconst twitter = publicRuntimeConfig.TWITTER;\n\nconst PageHead: NextPage<Props> = ({ page, description, image }: Props) => {\n  const pageTitle = page ? `${page} | ${appName}` : appName;\n  const pageDescription = description ?? appDescription;\n\n  let pageUrl;\n  let isItemPage;\n\n  if (typeof window !== "undefined") {\n    pageUrl = window.location.href ?? appUrl;\n    isItemPage = window.location.pathname.includes("item");\n  }\n\n  const disallowRobot = isItemPage ? true : false;\n\n  const pageImage = image ?? {\n    url: `${appUrl}/logo.png`,\n    width: 400,\n    height: 400,\n    alt: `${appName} logo`,\n  };\n\n  return (\n    <NextSeo\n      title={pageTitle}\n      description={pageDescription}\n      canonical={pageUrl}\n      openGraph={{\n        url: pageUrl,\n        title: pageTitle,\n        description: pageDescription,\n        images: [pageImage],\n        site_name: appName,\n      }}\n      twitter={{\n        handle: twitter,\n        site: twitter,\n        cardType: "summary_large_image",\n      }}\n      noindex={disallowRobot}\n      nofollow={disallowRobot}\n    />\n  );\n};\n\nexport default PageHead;\n\n
Run Code Online (Sandbox Code Playgroud)\n

以下是它在组件/页面中的使用方式。此页面具有来自第三方 API 的动态内容,并且 URL 取决于该内容。我不确定,但我认为这个页面是通过静态生成创建的:

\n
import Head from "../../components/Head";\n\ninterface Props {\n  item: ContentProps;\n}\n\nconst MostLikedThings: NextPage<Props> = ({ item }: Props) => {\n  ...\n\n  return (\n    <>\n      <Head\n        page={item.title}\n        description={item.synopsis}\n        image={...}\n      />\n      <MainWrapper>\n          ...\n      </MainWrapper>\n    </>\n  );\n};\n\nexport default MostLikedThings;\n\nexport async function getStaticPaths() {\n  return {\n    paths: [],\n    fallback: true,\n  };\n}\n\nexport async function getStaticProps({ params }: { params: { id: string } }) {\n  const item = await (\n    await fetch(`/api/content`)\n  ).json();\n  return {\n    props: { item },\n    revalidate: 86400,\n  };\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

不需要外部内容的更简单的页面如下所示。我相信它也是用静态生成制作的,因为(据我所知)我没有使用过getServerSideProps它:

\n
import Head from "../components/Head";\nimport Main from "../apps/main/index";\nimport { NextPage } from "next";\n\nconst Home: NextPage = () => {\n\n  return (\n    <>\n      <Head page="Home" />\n      <Main />\n    </>\n  );\n};\n\nexport default Home;\n\n
Run Code Online (Sandbox Code Playgroud)\n

我相信这两个页面都是通过静态生成生成的,对吧?在这种情况下,它们都是在构建时生成的。这意味着元标记应该位于源代码中。那么,为什么他们不呢?

\n

进行了多次尝试

\n

我已经尝试了很多事情,包括:

\n
    \n
  • 试图getServerSideProps以某种方式更明确地确保页面是在服务器端生成的
  • \n
  • Head从 _app.tsx 和 _document.tsx 中删除
  • \n
  • head仅使用页面中的html 标签来手动设置元标签
  • \n
  • 使用next/head而不是直接在页面中使用 next-seo 来手动设置元标记:
  • \n
\n
<Head>\n        ...\n        <meta name="twitter:card" content="summary_large_image" />\n        <meta name="twitter:site" content={twitter} />\n        <meta name="twitter:creator" content={twitter} />\n        ...\n</Head>\n
Run Code Online (Sandbox Code Playgroud)\n

他们都没有工作。

\n

最后要注意的是,我在这里读到:

\n
\n

标题、元或任何其他元素(例如脚本)需要包含\n为 Head 元素的直接子元素,或包装到 <React.Fragment> 或数组\n的最大一级\n\xe2\x80\x94,否则标签将无效\'无法在客户端导航上\n正确拾取。

\n
\n

我想我已经确定了这一点。

\n

我读过的许多问题中的一些:

\n\n

我错过了什么吗?

\n

谢谢。

\n

nus*_*ara 11

在无数次根据其他人未回答的问题进行实验后,我发现我有两个问题。

首先要注意的一件事:主页肯定是在构建时创建的(静态生成)。在我的问题中我不确定。现在我确定了。

解决问题。

加载画面问题

在数据加载之前有一个加载屏幕是一种常见的模式。如果我们有一个加载屏幕,那就是在构建时创建的。

我的组件是这样嵌套的:

    <Layout> // loading screen here
      <Navbar />
      <Main>{children!}</Main> // NextSeo here
    </Layout>
Run Code Online (Sandbox Code Playgroud)

加载屏幕位于<Layout />但我的<NextSeo />实现位于<Main />. 这是我在布局中的加载代码:

  if (user && user.loading)
    return (
      <Loading />
    );
Run Code Online (Sandbox Code Playgroud)

因此,即使浏览器在运行时正确渲染了页面,Next.js 在构建时也只构建了 Loading 组件。可能是因为这是它收到的第一件东西。这就是各种元标记验证器抓取的内容。

不幸的是,当我写问题时我并不知道这一点,所以可能没有人能够弄清楚这一点,因为我不知道如何嵌套所有组件和实现。我的错。

就我而言,我有点弃用用户加载的东西,并能够用这个解决方案替换它:

  const router = useRouter();

  const [pageLoading, setPageLoading] = useState<boolean>(false);

  useEffect(() => {
    const handleStart = () => {
      setPageLoading(true);
    };
    const handleComplete = () => {
      setPageLoading(false);
    };

    router.events.on("routeChangeStart", handleStart);
    router.events.on("routeChangeComplete", handleComplete);
    router.events.on("routeChangeError", handleComplete);
  }, [router]);
Run Code Online (Sandbox Code Playgroud)

如果我确实需要再次获得用户数据,我可能getServerSideProps还必须使用useRouter. 我还不知道。这只是说您的用例可能需要与我的稍有不同的东西。

getServerSideProps 还是 getStaticProps?

getStaticPath我的第二个问题是对和的误用/误解getStaticProps。根据文档

getStaticPaths(静态生成):根据数据指定预渲染页面的动态路由。

我的用例涉及从第三方 API 获取动态 ID,然后使用它来构建 URL 并为其获取内容。所以我一起使用了 getStaticPath 和 getStaticProps 来做到这一点。这些在构建时创建页面。因此,页面从未收到元标记所需的数据。

因为文档明确关于使用这两种方法的动态路径,但在涉及 getServerSideProps 时就不那么明确了,所以我的印象是不应该使用 getServerSideProps 来完成此操作。

事实证明,我错了。我们可以将 getServerSideProps 与动态路径一起使用。所以代替这个:

export async function getStaticPaths() {
  return {
    paths: [],
    fallback: true,
  };
}

export async function getStaticProps({ params }: { params: { id: string } }) {
  let item = await (
    await fetch(
      `/api/content/?id=${params.id}`
    )
  ).json();

  return {
    props: { item },
    revalidate: 86400,
  };
}
Run Code Online (Sandbox Code Playgroud)

我现在这样做:

export async function getServerSideProps({
  params,
}: {
  params: { id: string };
}) {
  let item = await (
    await fetch(
      `/api/content/?id=${params.id}`
    )
  ).json();

  return {
    props: { item },
  };
}
Run Code Online (Sandbox Code Playgroud)

因为 getServerSideProps 根据请求生成页面,所以它每次都会重新提供元标记。