Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
754 views
in Technique[技术] by (71.8m points)

reactjs - React SSR blinks when starting client

simplifying my post:

my ssr webpage blinks when starting client which means page renders server side rendered html then goes blank and then it starts loading everything all over again.

going through the details:

i'm working on a react project which we decided to change it from being rendered in client to render in server. the project includes react-router-dom ,redux and react-redux ,material-ui which comes with react-jss ,loadable/component ,also handling the head elements by react-helmet-async ,and in ssr it's using express.js which seems to be a must.

  • for react-router-dom i did everything that is on the docs. using BrowserRouter in client and StaticRouter in ssr and passing a context object to it.
  • for redux and react-redux i saved preloaded_state in a variable in window and fetched it client side then pass it to store.also fetched some external data to get the content on the source of the page.so i have some requests and data fetching in ssr.
  • for material-ui i created a new serverSideStyleSheet and collected all the styles from all over the project.
  • for react-helmet-async i've got different Helmet tags for each page that collects different title ,description and ... individualy.there is also a helmetProvider wrapper for csr and ssr.
  • at first i used react-helmet but it wasn't compatible with renderToNodeStream.i didn't change react-helmet-async although i'm not using renderToNodeStream but kept it to migrate to renderToNodeStream one day hopefully.
  • about express.js i'm using it as the docs say but after i added loadable/component i wasn't able to get a successful server side rendering by just adding app.get('*' , ServerSideRender) .so i had to add every url i wanted to render in server in app.get(url ,ServerSideRender).
  • the other thing about the project is that i didn't eject and created it with create-react-app and there is no webpack config or babelrc but instead i'm using craco.config.js
  • the last thing is that i've excluded index.html from ssr and instead i've made the tags myself in SSR.js file so index.html gets rendered just in client. and i was so careful about writing tags in ssr exactlly like they are in index.html

solution but not the solution:

this problem is occuring because i use loadable component in my Router.js. so when i import components the normal way there is no blink and everything is fine but unused js decreases my page's perfomance score. i need loadable component stop making the page blink.

diving into the code:

just the client

index.html : rendered just in client

<!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 : rendered just in client

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")
    );
})

just the server

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')
    }
})

for both client and server

App.js

<div>
    <Header/>
    <Router/>
    <Footer/>
</div>

i really mean it when i say thank you for your time . i'd be more than happy to hear any suggestion or solution.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...