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