Я выполняю рендеринг приложения 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
Редактировать
Похоже, что react-helmet
небезопасно для потоков согласно этой проблеме < / а>. Есть ли возможность создать обходной путь на основе этого знания?
FirstView
иSecondView
на разных маршрутах? - person Gaurav Saraswat   schedule 18.02.2019)
дает вам строковый объект, который изменяет содержимое экспортированного/index.html
. Можете ли вы сказать мне, какой загрузчик вы используете, чтобы требовать.html
файлов - person Gaurav Saraswat   schedule 19.02.2019webpack.config.js
(я используюhtml-loader
) - person Limbo   schedule 19.02.2019can you try to just require the html file only once at top with imports
instead of again and again in run function
иassign it to template private variable inside the promise returned in run function
- person Gaurav Saraswat   schedule 19.02.2019