4

简化我的帖子:

我的 ssr 网页在启动客户端时闪烁,这意味着页面呈现服务器端呈现的 html,然后变为空白,然后重新开始加载所有内容。

浏览细节:

我正在做一个反应项目,我们决定将其从在客户端呈现更改为在服务器中呈现。该项目包括react-router-dom,reduxreact-redux,material-ui附带react-jss,loadable/component也处理 head 元素react-helmet-async, 并且在 ssr 中使用express.js这似乎是必须的。

  • 因为react-router-dom我做了文档上的所有事情。BrowserRouter在客户端和StaticRouterssr 中使用并将context对象传递给它。
  • 对于redux并且react-redux我保存preloaded_state在窗口中的一个变量中并在客户端获取它然后将其传递给存储。还获取了一些外部数据以获取页面源上的内容。所以我在 ssr 中有一些请求和数据获取。
  • 因为material-ui我创建了一个new serverSideStyleSheet并收集了整个项目的所有样式。
  • 因为react-helmet-asyncHelmet为每个页面设置了不同的标签,这些标签收集了不同的标题、描述和......个性化。还有一个helmetProvidercsr 和 ssr 的包装器。
  • 起初我使用react-helmet但它与renderToNodeStream.i 不兼容。我没有更改 react-helmet-async 虽然我没有使用 renderToNodeStream 但renderToNodeStream希望它能够迁移到某一天。
  • 关于express.js我按照文档所说的那样使用它,但是在我添加之后,我loadable/component无法通过添加 . 所以 app.get('*' , ServerSideRender)我必须添加我想在服务器中呈现的每个 url app.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>

当我说谢谢你的时间时,我是认真的。我很乐意听到任何建议或解决方案。

4

0 回答 0