Razzle with React Helmet:当使用来自 Axios 的动态值时,元标记在爬虫中显示错误

Razzle with React Helmet: Meta tags displayed wrong in crawlers when using dynamic values from Axios

我正在使用 Razzle for using React and Server Side Rendering with React Helmet。当您使用 React Helmet 设置具有动态值的元标记时,我遇到了这个问题,它没有正确显示。但是,如果您使用静态值设置元标记,它就会起作用。

请看一些代码。

SEO.js 组件

import React, { Component } from 'react';
import { Helmet } from "react-helmet-async";

class SEO extends Component {
    constructor(props) {
        super(props);
        this.state = {
            title: this.props.title,
            description: this.props.description,
            image: this.props.image
        }
    }

    shouldComponentUpdate(nextProps) {
        if(this.props != nextProps) {
            this.setState({
                title: nextProps.title,
                description: this.props.description,
                image: nextProps.image
            })
            return true;
        } else {
            return false;
        }
    }

    render() { 
        return (
            <div>
                <Helmet>
                    <title>{this.state.title ? this.state.title : "Volunteer Hub by Indorelawan"}</title>
                    <meta name="title" content={this.state.title ? this.state.title : "Volunteer Hub by Indorelawan"} />
                    <meta
                    name="description"
                    content={this.state.description ? this.state.description : "Volunteer Hub by Indorelawan adalah tempat kolaborasi antara relawan dan komunitas sosial yang memiliki semangat kerelawanan dan gotong royong untuk Indonesia."}
                    />
                    <meta
                    property="og:title"
                    content={this.state.title ? this.state.title : "Volunteer Hub by Indorelawan"}
                    />
                    <meta
                    property="og:description"
                    content={this.state.description ? this.state.description : "Volunteer Hub by Indorelawan adalah tempat kolaborasi antara relawan dan komunitas sosial yang memiliki semangat kerelawanan dan gotong royong untuk Indonesia."}
                    />
                    <meta
                    property="og:image"
                    content={this.state.image ? this.state.image : "https://volunteerhub.id/assets/logo/seo.jpg"}
                    />
                    <meta property="og:url" content="https://volunteerhub.id" />
                    <meta
                    name="twitter:title"
                    content={this.state.title ? this.state.title : "Volunteer Hub by Indorelawan"}
                    />
                    <meta
                    name="twitter:description"
                    content={this.state.description ? this.state.description : "Volunteer Hub by Indorelawan adalah tempat kolaborasi antara relawan dan komunitas sosial yang memiliki semangat kerelawanan dan gotong royong untuk Indonesia."}
                    />
                    <meta
                    name="twitter:image"
                    content={this.state.image ? this.state.image : "https://volunteerhub.id/assets/logo/seo.jpg"}
                    />
                    <meta name="twitter:card" content="summary_large_image" />
                </Helmet>
            </div>
        );
    }
}

export default SEO;

以下是设置静态元标记的示例:

import React, {Component} from "react";
import SEO from "../../components/SEO";

class ScheduleContainer extends Component {
    constructor(props) { super(props); }
    render() {
        return(
            <div>
                <SEO 
                    title="Cek Jadwal | Volunteer Hub by Indorelawan"
                    description="Cek jadwal kegiatan di Volunteer Hub! Volunteer Hub by Indorelawan adalah tempat kolaborasi antara relawan dan komunitas sosial yang memiliki semangat kerelawanan dan gotong royong untuk Indonesia." />
            </div>);
    }
}

下面是设置动态元标记的示例:

import React, {Component} from "react";
import axios from "axios";
import SEO from "../../components/SEO";

class EventContainer extends Component {
    constructor(props) {
        super(props);
        this.state = {
            event: {}
        }
    }

    componentDidMount() {
        axios.get('API_URL')
        .then(response => {
            this.setState({ event: response.data.result })
        });
    }

    render() {
        return(
            <div>
                <SEO 
                    title={this.state.event.title}
                    description={this.state.event.description} />
            </div>);
    }
}

Server.js

import RootContainer from "./containers/RootContainer";
import React from "react";
import { StaticRouter } from "react-router-dom";
import express from "express";
import { renderToString } from "react-dom/server";
import { Helmet, HelmetProvider } from "react-helmet-async";

const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);

const server = express();
server
  .disable("x-powered-by")
  .use(express.static(process.env.RAZZLE_PUBLIC_DIR))
  .get("/*", (req, res) => {
    const context = {};
    const helmetContext = {};
    const markup = renderToString(
      <HelmetProvider context={helmetContext}>
        <StaticRouter context={context} location={req.url}>
          <RootContainer />
        </StaticRouter>
      </HelmetProvider>
    );

    const { helmet } = helmetContext;

    if (context.url) {
      res.redirect(context.url);
    } else {
      res.status(200).send(
        `<!doctype html>
        <html lang="">
        <head>
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <meta http-equiv="X-UA-Compatible" content="ie=edge" />
            <meta
            name="keywords"
            content="volunteer, hub, by, indorelawan, volunteer hub, volunteer hub by indorelawan, kolaborasi, dimulai, dari, sini, ubah, niat, baik, jadi, aksi, baik, hari, ini"
            />
            <meta name="robots" content="index, follow" />
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
            <meta name="language" content="Indonesia" />
            <meta name="author" content="Indorelawan" />
            <meta name="msapplication-TileColor" content="#ffffff">
            <meta name="theme-color" content="#222222" />
            ${helmet.title.toString()}
            ${helmet.meta.toString()}
            ${
        assets.client.css
          ? `<link rel="stylesheet" href="${assets.client.css}">`
          : ""
        }
            ${
        process.env.NODE_ENV === "production"
          ? `<script src="${assets.client.js}" defer></script>`
          : `<script src="${
          assets.client.js
          }" defer crossorigin></script>`
        }
        ...
        </head>
        <body>
            <div id="root">${markup}</div>
            <script>
            if ("serviceWorker" in navigator) {
              if (navigator.serviceWorker.controller) {
                console.log("[PWA Builder] active service worker found, no need to register");
              } else {
                // Register the service worker
                navigator.serviceWorker
                  .register("pwabuilder-sw.js", {
                    scope: "./"
                  })
                  .then(function (reg) {
                    console.log("[PWA Builder] Service worker has been registered for scope: " + reg.scope);
                  });
              }
            }
            </script>
        </body>
    </html>`
      );
    }
  });

export default server;

现在您已经看到了代码,这是我复制粘贴到 Google SERP 模拟器和 WhatsApp 时的结果:

静态元标记来自 Schedule Page:

Google SERP 模拟器 WhatsApp的

动态元标记来自 Event Page:

Google SERP 模拟器 WhatsApp的

从结果来看,它总是return 默认的标题和描述标签,而不是从 axios 传递的标题和描述。这是正常行为还是我做错了什么?

原来 Razzle 不是服务器端渲染。您要么必须使用自定义快递服务器定义 SEO 标签,要么只使用 SSR for React。

我正在为此使用 NextJS,这没问题。

Razzle IS 服务器端渲染,您的动态元标记方案的问题在于您依赖于在 componentDidMount 中获取的数据,而 componentDidMount 作为提交阶段的生命周期方法,不会在服务器,因为服务器端没有实际安装。

NextJS为你解决了这个问题,因为getInitialProps是在服务端和客户端调用的