如何在没有 SSR 或预渲染的情况下为 React SPA 实现 SEO。并且最好保持代码的可移植性,例如没有供应商锁定

How to achieve SEO for React SPA without SSR or prerendering. And preferably keep the code portable e.g. no vendor lock-in

SEO 话题已经争论了很长时间,React SPA 非常普遍。然而,搜索 SO 并没有得到明确的编码指导,也没有得到简单易懂的部署 React SPA 和实现 SEO 所需的特定实用步骤序列的补充。

现有的 Q/A 不是很有用,就是使用链接。这个 question 与 React 无关,详细答案考虑弃用 AJAX 技术。虽然它的点赞数和浏览量显示了这个话题的重要性。

超越 SO 的搜索产生了官方的 Create React App (CRA) page。为了创建一个最小的可重现示例,我遵循了以下步骤:

  1. 执行的命令:
yarn create react-app my-cra
cd my-cra
yarn add react-router-dom
  1. 用代码替换了生成的 src/App.js 文件的内容:
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Page1 from './Page1';
import Page2 from './Page2';

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Page1 />} />
        <Route path="test" element={<Page2 />} />
      </Routes>
    </BrowserRouter>
  );
}
  1. 添加了一个文件src\Page1.js:
import { Link } from "react-router-dom";

const Page1 = () => {
  return (
    <>
      <h1>Page1</h1>
      <Link to="/test">To Page2</Link>
    </>
  );
};

export default Page1;
  1. 添加了一个文件src\Page2.js:
import { Link } from "react-router-dom";

const Page2 = () => {
  return (
    <>
      <h1>Page2</h1>
      <Link to="/">Back to Page1</Link>
    </>
  );
};

export default Page2;

React SPA 按预期工作并且自然地使用客户端路由。 GitHub 页面 deployment was chosen since it doesn't require to introduce vendor specific code. However the problem is that according to the Notes it will be necessary to use either routing with hashes or this repo. Hashes are not acceptable because Google explicitly disallows URLs with hashes for SEO. The repo is not an option either since the author suggests 如果 SEO 很重要,另一种解决方案。

所以问题是如何在不将步骤 2 中使用的 BrowserRouter 替换为简单的 React SPA 中的 HashRouter 的情况下,如何让 Googlebot 建立索引。另一种解决方案,除了 CRA + GitHub 可以建议页面。

答案旨在演示一组易于重现的步骤,无需任何 SSR 或预渲染即可获得由 Google 索引的 React SPA。它分为两部分,标题为:

  • 网站部署
  • 请求 Google 索引网站

第一部分是关于构建部署示例 React 应用程序。只有一个要点(涉及标题和规范)特定于 SEO。

第二部分是关于 SEO 的,但它并不特定于 SPA 或 React。

网站部署

部署基于 Crisp React 样板(我是作者)并使用 Cloudflare Pages。

This particular boilerplate has been chosen due to its features, like the variety of deployments, both Jamstack and full stack. It makes it easy to alter the deployment described below and switch from Jamstack to full stack if need be. The ready-to-replace Structured Data placeholders provided by the boilerplate could help with further SEO improvements once the website is indexed by Google.

步骤:

  • 克隆 Crisp React 存储库:

    git clone https://github.com/winwiz1/crisp-react.git
    cd crisp-react
    
  • 通过将 code fragment 替换为以下代码来简化配置:

    /****************** Start SPA Configuration ******************/
      var SPAs = [
        new SPA({
          name: "index",
          entryPoint: "./src/entrypoints/first.tsx",
          ssr: false,
          redirect: true
        })];
    
      SPAs.appTitle = "Crisp React";
    /****************** End SPA Configuration ******************/
    
    

    另外将 "Crisp React" 替换为您的 SPA 标题。一个独特而合理的标题对 SEO 很重要。

  • 检查客户端代码库以确保每个页面都将 <title> HTML 元素和规范的 <meta> 标记设置为对您的网站有意义的值。这可以通过在所有 client/src/components/*.tsx 文件中搜索 <Helmet> 模式并查看相关代码来完成:

    <Helmet>
      <title>{getTitle(pageName)}</title>
      <link rel="canonical" href={getCanonical()} />
    </Helmet>
    
    // Simplified code
    export const getCanonical = (pagePath?: string): string|undefined => {
      return !!pagePath? (window.location.origin + pagePath) : window.location.href;
    }
    // Simplified code
    export const getTitle = (pageTitle?: string): string => {
      return !!pageTitle? `${SPAs.appTitle} - ${pageTitle}` : SPAs.appTitle;
    }
    
  • 提交更改:

    git add client
    git commit -m "Changed configuration"
    
  • 通过访问 repo.new 创建一个新的 GitHub 存储库。

  • 将克隆的存储库指向新创建的存储库并将其推送到那里:

    git remote set-url origin https://github.com/your-github-username/your-newly-created-repo
    git push
    
  • 通过登录 Cloudflare dashboard 并创建 Cloudflare Pages 项目部署到 Cloudflare Pages。

    This step will take several minutes spent mostly on waiting. It should take around a minute to copy the data provided below and paste it into the single configuration screen presented by Pages.

    使用Menu > Pages > Create a project。系统将要求您授权 read-only 访问您的 GitHub 存储库,并可选择缩小对特定存储库的访问权限。 Select 您在上一步中推送到 GitHub 的存储库,并在“设置构建和部署”屏幕上提供以下信息:

    Configuration option Value
    Production branch master
    Build command yarn build:jamstack
    Build output directory client/dist

    添加以下环境变量:

    Environment variable Value
    NODE_VERSION 16.14.0

    您可以选择自定义“项目名称”字段。它默认为 GitHub 存储库名称,用于创建子域,例如<project-name>.pages.dev.

    完成配置后,单击“保存并部署”按钮。部署管道完成后,将浏览器指向 https://<project-name>.pages.dev 以检查网站是否在线。

    最后使用 'Custom domains' 选项卡将部署的网站映射到您拥有的域。

请求 Google 索引网站

在网页中添加一些原创内容以避免duplicate content.

您可以选择 passive approach and simply wait until Googlebot discovers your website or proactively ask Google to index it. If you choose the latter, use Google Search Console (GSC):

  • Add 使用“+ 添加 属性”菜单将您的自定义域添加到 GSC。
  • 单击“URL 检查”菜单以激活 URL Inspection Tool 并键入您要编制索引的页面的路径。响应将声明“URL 不在 Google 上”,告诉您该页面尚未编入索引。
  • 单击 "TEST LIVE URL" 按钮确认页面可以编入索引。 (可选)查看 GSC 呈现的页面的屏幕截图。
  • 通过单击 "REQUEST INDEXING" link 请求为页面建立索引。响应应该说您的请求已添加到优先抓取 queue.

必须为每个 SPA 页面重复最后 3 个步骤。


部署的网站缺少常用的,但对于 SEO 文件不是绝对必要的,例如 sitemap.xmlrobots.txt。站点地图对于被动方法更为重要。 robots.txt 文件不是必需的,除非您想设置一些爬行限制。

该网站未使用所有 Crisp React 功能,例如能够将 React 应用程序拆分为多个 SPA,并有选择地预呈现每个 SPA 的 landing/index 页面以获得更好的性能。如果您需要此功能以及 SEO,请考虑切换到完整堆栈构建或使用 Cloudflare Worker,如 this article.

中所述