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此页面源应该包含用于描述、标题、图像和其他一些内容的 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>\nRun Code Online (Sandbox Code Playgroud)\n让我困惑的是 Next.js 没有渲染它们。根据文档:
\n\n\n默认情况下,Next.js 预渲染每个页面。这意味着 Next.js\n提前为每个页面生成 HTML,而不是\n由客户端 JavaScript 完成这一切。预渲染可以带来更好的\n性能和 SEO。
\n
除非我误读了这一点,否则这意味着只要我使用默认设置,页面就会通过 SSR 或静态生成来呈现。正确的?
\n此外,上面的来源提到"nextExport":true,"autoExport":true,我认为这表明该页面应该在构建时创建。
我的代码可能经历了一些更改,但我很确定它们没有偏离 SSR 或静态生成。
\n在某个时候,我添加了 _document.tsx 和 _app.tsx。
\n我没有对 _document.tsx 进行太多更改。Head经过一些实验,我认为从这里开始是至关重要的next/document:
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};\nRun Code Online (Sandbox Code Playgroud)\n_app.tsx 有更多更改,但我认为不会影响元标记。如果我在这里手动放置元标记Head,它们会显示在源代码中。对于默认设置效果很好,但我希望能够以编程方式为每个页面执行此操作。
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}\nRun Code Online (Sandbox Code Playgroud)\n我Head使用 next-seo 创建了一个组件。它做了相当多的工作。也许只需注意它直接返回NextSeo,而不将其放入 React 片段或其他组件中:
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\nRun Code Online (Sandbox Code Playgroud)\n以下是它在组件/页面中的使用方式。此页面具有来自第三方 API 的动态内容,并且 URL 取决于该内容。我不确定,但我认为这个页面是通过静态生成创建的:
\nimport 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\nRun Code Online (Sandbox Code Playgroud)\n不需要外部内容的更简单的页面如下所示。我相信它也是用静态生成制作的,因为(据我所知)我没有使用过getServerSideProps它:
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\nRun Code Online (Sandbox Code Playgroud)\n我相信这两个页面都是通过静态生成生成的,对吧?在这种情况下,它们都是在构建时生成的。这意味着元标记应该位于源代码中。那么,为什么他们不呢?
\n我已经尝试了很多事情,包括:
\ngetServerSideProps以某种方式更明确地确保页面是在服务器端生成的Head从 _app.tsx 和 _document.tsx 中删除head仅使用页面中的html 标签来手动设置元标签next/head而不是直接在页面中使用 next-seo 来手动设置元标记:<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>\nRun Code Online (Sandbox Code Playgroud)\n他们都没有工作。
\n最后要注意的是,我在这里读到:
\n\n\n标题、元或任何其他元素(例如脚本)需要包含\n为 Head 元素的直接子元素,或包装到 <React.Fragment> 或数组\n的最大一级\n\xe2\x80\x94,否则标签将无效\'无法在客户端导航上\n正确拾取。
\n
我想我已经确定了这一点。
\n我读过的许多问题中的一些:
\n我错过了什么吗?
\n谢谢。
\nnus*_*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. 我还不知道。这只是说您的用例可能需要与我的稍有不同的东西。
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 根据请求生成页面,所以它每次都会重新提供元标记。
| 归档时间: |
|
| 查看次数: |
4872 次 |
| 最近记录: |