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是在服务端和客户端调用的
我正在使用 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 模拟器
动态元标记来自 Event Page:
Google SERP 模拟器
从结果来看,它总是return 默认的标题和描述标签,而不是从 axios 传递的标题和描述。这是正常行为还是我做错了什么?
原来 Razzle 不是服务器端渲染。您要么必须使用自定义快递服务器定义 SEO 标签,要么只使用 SSR for React。
我正在为此使用 NextJS,这没问题。
Razzle IS 服务器端渲染,您的动态元标记方案的问题在于您依赖于在 componentDidMount 中获取的数据,而 componentDidMount 作为提交阶段的生命周期方法,不会在服务器,因为服务器端没有实际安装。
NextJS为你解决了这个问题,因为getInitialProps是在服务端和客户端调用的