我正在运行React应用程序的服务器端渲染。我为此使用express
。整个服务器端渲染代码如下:
import * as React from "react"
import * as ReactDOMServer from "react-dom/server"
import * as express from "express"
import { StaticRouter } from "react-router-dom"
import walker = require("react-tree-walker")
import { useStaticRendering } from "mobx-react"
import Helmet from "react-helmet"
import Provider from "../src/Provider"
import { StaticRouterContext } from "react-router"
import version = require("../version")
var _template: string = require(`../dist/${version.v()}/index.html`)
interface IRenderResponse {
statusCode: number,
template: string,
redirect?: string
}
const run = (url: string, locale?: string): Promise<IRenderResponse> => {
var template: string
var html: string = ""
var head: object
var context: StaticRouterContext = {}
useStaticRendering(true)
var routing = (
<StaticRouter
location={url}
context={context}
>
<Provider defaultLocale={locale} />
</StaticRouter>
)
return new Promise((resolve) => {
walker(routing, (element, instance) => {
if (instance && typeof instance._prepare == typeof (() => {}))
return instance._prepare()
}).then(() => {
html = ReactDOMServer.renderToString(routing)
head = Helmet.renderStatic()
template = _template.replace(/\${__rh-([a-z]+)}/gi, (match, group) => {
return head[group].toString()
})
template = template.replace(/\${__body}/gi, (match) => {
return html
})
if (context.url)
context["statusCode"] = 301
resolve({
statusCode: context["statusCode"] || 200,
template,
redirect: context.url
})
}).catch((error) => {
template = _template.replace(/\${__rh-([a-z]+)}/gi, "")
template = template.replace(/\${__body}/gi, error.stack || error.toString())
resolve({
statusCode: 500,
template
})
})
})
}
var app = express()
app.get("*", (req, res) => {
var accepted = req.acceptsLanguages()
var locale = accepted ? (accepted[0] || "ru").split("-")[0] : "ru"
run(req.originalUrl, locale).then((data) => {
if (data.redirect)
res.redirect(data.redirect)
else
res.status(data.statusCode).send(data.template)
})
})
app.listen(1239)
您可以看到此处使用了react-tree-walker
。但是无论我在服务器端渲染中使用什么,都会发生此问题。
问题在于,如果我的node-js
服务器在一个线程中运行,那么如果同时执行两个不同的请求,则react-helmet
会混合使用字段。例如,如果有两个视图:
class FirstView extends React.Component {
render() {
return (
<Helmet>
<title>This is first title</title>
<meta name="description" content="My first view description" />
</Helmet>
)
}
}
和
class SecondView extends React.Component {
render() {
return (
<Helmet>
<title>This is second title</title>
<meta name="description" content="My second view description" />
</Helmet>
)
}
}
然后我可以收到这样的东西:
<title>This is first title</title>
<meta name="description" content="My second view description" />
很显然,发生这种情况是因为react-helmet
使用静态字段。因此,如果并行处理两个请求,则该字段将混乱地更改。
我怎么打败它?对于高负载项目,通常会出现此问题,这可能会导致SEO崩溃,因为搜寻器可能会收到错误的数据。
webpack.config.js
:
var webpack = require("webpack")
var config = {
mode: "production",
target: "node",
entry: __dirname + "/index.tsx",
output: {
path: __dirname,
filename: `index.js`
},
module: {
rules: [
{
test: require.resolve("phoenix"),
use: "imports-loader?window=>global"
},
{
test: /\.tsx?$/,
loader: "awesome-typescript-loader?configFileName=tsconfig.server.json",
exclude: /node_modules/
},
{
test: /\.js$/,
enforce: "pre",
loader: "source-map-loader"
},
{
test: /\.html$/,
loader: "html-loader"
},
{
test: /\.(svg|woff|woff2|ttf|otf|png|jpg)$/,
loader: "url-loader"
},
{
test: /\.(css|sass)$/,
loader: "ignore-loader"
}
]
},
resolve: {
modules: [
"node_modules",
`${__dirname}/../src`
],
extensions: [".js", ".jsx", ".sass", ".json", ".css", ".ts", ".tsx"]
},
parallelism: 2,
plugins: [
new webpack.DefinePlugin({
"ENV": JSON.stringify("server"),
"process.env.ENV": JSON.stringify("server"),
"process.env.VERSION": JSON.stringify("n/a"),
"process.env.NODE_ENV": JSON.stringify("production"),
"global.GENTLY": false
})
]
}
module.exports = config
根据this issue,似乎react-helmet
是线程不安全。是否有可能基于此知识创建一些解决方法?
答案 0 :(得分:5)
我觉得require(../dist/${version.v()}/index.html);
仅导入模板的一个实例。
通过在所有网络调用中反复要求模板,您可能会一次又一次地更新相同的字符串引用。
所以。您可以执行以下操作:
import(require) template
仅一次在文件顶部。基本代码段:
...
import version = require("../version");
import appTemplate = require(`../dist/${version.v()}/index.html`)
interface IRenderResponse {
statusCode: number,
template: string,
redirect?: string
}
const run = (url: string, locale?: string): Promise<IRenderResponse> => {
var template: string = "";
var html: string = ""
var head: object
var context: StaticRouterContext = {}
useStaticRendering(true)
var routing = (
<StaticRouter
location={url}
context={context}
>
<Provider defaultLocale={locale} />
</StaticRouter>
)
return new Promise((resolve) => {
walker(routing, (element, instance) => {
if (instance && typeof instance._prepare == typeof (() => {}))
return instance._prepare()
}).then(() => {
html = ReactDOMServer.renderToString(routing)
head = Helmet.renderStatic()
template = appTemplate.replace(/\${__rh-([a-z]+)}/gi, (match, group) => {
return head[group].toString()
})
template = template.replace(/\${__body}/gi, (match) => {
return html
})
if (context.url)
context["statusCode"] = 301
resolve({
statusCode: context["statusCode"] || 200,
template,
redirect: context.url
})
})....