简化我的帖子:
我的 ssr 网页在启动客户端时闪烁,这意味着页面呈现服务器端呈现的 html,然后变为空白,然后重新开始加载所有内容。
浏览细节:
我正在做一个反应项目,我们决定将其从在客户端呈现更改为在服务器中呈现。该项目包括react-router-dom
,redux
和react-redux
,material-ui
附带react-jss
,loadable/component
也处理 head 元素react-helmet-async
, 并且在 ssr 中使用express.js
这似乎是必须的。
- 因为
react-router-dom
我做了文档上的所有事情。BrowserRouter
在客户端和StaticRouter
ssr 中使用并将context
对象传递给它。 - 对于
redux
并且react-redux
我保存preloaded_state
在窗口中的一个变量中并在客户端获取它然后将其传递给存储。还获取了一些外部数据以获取页面源上的内容。所以我在 ssr 中有一些请求和数据获取。 - 因为
material-ui
我创建了一个new serverSideStyleSheet
并收集了整个项目的所有样式。 - 因为
react-helmet-async
我Helmet
为每个页面设置了不同的标签,这些标签收集了不同的标题、描述和......个性化。还有一个helmetProvider
csr 和 ssr 的包装器。 - 起初我使用
react-helmet
但它与renderToNodeStream
.i 不兼容。我没有更改 react-helmet-async 虽然我没有使用 renderToNodeStream 但renderToNodeStream
希望它能够迁移到某一天。 - 关于
express.js
我按照文档所说的那样使用它,但是在我添加之后,我loadable/component
无法通过添加 . 所以app.get('*' , ServerSideRender)
我必须添加我想在服务器中呈现的每个 urlapp.get(url ,ServerSideRender)
。 - 关于该项目的另一件事是我没有弹出并使用 create-react-app 创建它并且没有 webpack 配置或 babelrc 但我使用的是 craco.config.js
- 最后一件事是我已经从 ssr 中排除了 index.html,而是我自己在 SSR.js 文件中制作了标签,因此 index.html 仅在客户端中呈现。我非常小心地在 ssr 中编写标签,就像它们在 index.html 中一样
解决方案但不是解决方案:
出现这个问题是因为我在我的Router.js
. 所以当我以正常方式导入组件时,没有闪烁,一切都很好,但未使用的 js 会降低我页面的性能分数。我需要可加载组件停止使页面闪烁。
潜入代码:
只是客户
index.html :仅在客户端呈现
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta name="robots" content="noindex, nofollow" />
<meta data-rh="true" name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no" />
<link href="%PUBLIC_URL%/fonts.css" rel="stylesheet" />
</head>
<body>
<div id="root"></div>
<script src="%PUBLIC_URL%/main.js"></script>
</body>
</html>
index.js :仅在客户端呈现
import React from 'react'
import ReactDOM from 'react-dom'
import {loadableReady} from '@loadable/component'
import App from './App'
import {BrowserRouter} from 'react-router-dom'
import {HelmetProvider} from 'react-helmet-async'
import { Provider } from 'react-redux'
loadableReady(() => {
const preloadedState = window.__PRELOADED_STATE__
delete window.__PRELOADED_STATE__
ReactDOM.hydrate(
<BrowserRouter>
<HelmetProvider>
<Provider store={store(preloadedState)}>
<App />
</Provider>{" "}
</HelmetProvider>
</BrowserRouter>,
document.getElementById("root")
);
})
只是服务器
ssrIndex.js
require('ignore-styles')
require('@babel/register')({
ignore: [/(node_modules)/],
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: [
'@babel/plugin-transform-runtime',
'@babel/plugin-proposal-class-properties',
'babel-plugin-dynamic-import-node',
'@babel/plugin-transform-modules-commonjs',
'@loadable/babel-plugin',
],
})
// Import the rest of our application.
require('./SSR.js')
SSR.js
import React from 'react'
import express from 'express'
import ReactDOMServer from 'react-dom/server'
import {StaticRouter} from 'react-router-dom'
import {Provider} from 'react-redux'
import ServerStyleSheets from '@material-ui/styles/ServerStyleSheets'
import {HelmetProvider} from 'react-helmet-async'
import {ChunkExtractor, ChunkExtractorManager} from '@loadable/server'
import path from 'path'
import App from './App'
import store from './redux/store'
import template from './utils/template'
const PORT = 8080
const app = express()
const renderPage = (req, res, preload) => {
const staticRouterContext = {}
const helmetContext = {}
const statsFile = path.resolve(__dirname, '../build', 'loadable-component.json')
const extractor = new ChunkExtractor({statsFile})
const sheets = new ServerStyleSheets()
const html = ReactDOMServer.renderToString(
sheets.collect(
<ChunkExtractorManager extractor={extractor}>
<HelmetProvider context={helmetContext}>
<StaticRouter location={req.url} context={staticRouterContext}>
<Provider store={store(preload)}>
<App />
</Provider>
</StaticRouter>
</HelmetProvider>
</ChunkExtractorManager>,
),
)
const {helmet} = helmetContext
const wholeData = template('scripts', {
chunks: html,
helmet,
extractor,
sheets,
preload,
})
res.send(wholeData)
}
const serverRenderer = (req, res, next) => {
fetchSomeExternalData()
.then(response => {
// response.data is used as preloaded data and passed to the store of redux
// also stored in a variable called __PRELOADED_STATE__ in window to use in client side
// to populate store of redux
renderPage(req, response, response.data)
})
.catch(err => {
// start server side rendering without preloaded data
renderPage(req, res)
})
}
// each url that i want to render on the server side i should add here individually
// which is not so convenient
app.get('/', serverRenderer)
app.get('/my-url-1/', serverRenderer)
app.get('/my-url-2/', serverRenderer)
app.use(express.static(path.join(__dirname, '/../build/')))
// the * doesnt seem to work
app.get('*', serverRenderer)
app.listen(PORT, () => {
if (process.send) {
process.send('ready')
}
})
对于客户端和服务器
应用程序.js
<div>
<Header/>
<Router/>
<Footer/>
</div>
当我说谢谢你的时间时,我是认真的。我很乐意听到任何建议或解决方案。