Material UI:Flickering on the initial load of the page(Razzle)
Asked Answered
U

1

11

I have integrated material UI in my Server side rendering app and have followed the code given here. Material UI css is been added to the html on the server side itself.

However, on the initial load, material ui styles are not applied which is causing it to flicker.

I tried using <cssbaseline> along with the below code but did not see any difference.

Here is my code: server.js

import App from '../common/containers/App';
import { Provider } from 'react-redux';
import React from 'react';
import configureStore from '../common/store/configureStore';
import express from 'express';
import qs from 'qs';
import { renderToString } from 'react-dom/server';
import serialize from 'serialize-javascript';
import { StaticRouter, matchPath } from 'react-router-dom';
import {theme} from '../theme';
import { ServerStyleSheets, ThemeProvider } from '@material-ui/styles';
import routes from './routes'
import "../common/assets/SCSS/app.scss";

const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);
const server = express();
server
  .disable('x-powered-by')
  .use(express.static(process.env.RAZZLE_PUBLIC_DIR))
  .get('/*', (req, res) => {
    const activeRoute = routes.find((route) => matchPath(req.url, route)) || {}
    const promise = activeRoute.fetchInitialData
      ? activeRoute.fetchInitialData(req.path)
      : Promise.resolve()
    promise.then((apiResult) => {
      const sheets = new ServerStyleSheets();
      const counter =  apiResult || 0;
      const preloadedState = { counter };
      const store = configureStore(preloadedState);
      const context = {};
      const markup = renderToString(
         sheets.collect(
           <Provider store={store}>
            <ThemeProvider theme={theme}>
              <StaticRouter location={req.url} context={context}>
                <App />
              </StaticRouter>
            </ThemeProvider>
          </Provider>
      ));
   const css = sheets.toString();
      const finalState = store.getState();
      const html = `<!doctype html>
          <html lang="">
          <head>
              ${assets.client.css
                ? `<link rel="stylesheet" href="${assets.client.css}">`
                : ''}
                ${css ? `<style id='jss-ssr'>${css}</style>` : ''}
                ${process.env.NODE_ENV === 'production'
                  ? `<script src="${assets.client.js}" defer></script>`
                  : `<script src="${assets.client.js}" defer crossorigin></script>`}
          </head>
          <body>
              <div id="root">${markup}</div>
              <script>
                window.__PRELOADED_STATE__ = ${serialize(finalState)}
              </script>
          </body>
      </html>`
 res.send(html);
    });
  });

export default server;

Client.js

import React,{useEffect} from 'react';
import { hydrate } from 'react-dom';
import {theme} from '../theme';
import { ThemeProvider } from '@material-ui/styles';
import { Provider } from 'react-redux';
import configureStore from '../common/store/configureStore';
import App from '../common/containers/App';
import { BrowserRouter } from "react-router-dom";
import "../common/assets/SCSS/app.scss";
import * as serviceWorker from '../serviceWorker';
import CssBaseline from '@material-ui/core/CssBaseline';

const store = configureStore(window.__PRELOADED_STATE__);
const Client = () => {
  useEffect(() => {
   
    const jssStyles = document.querySelector('#jss-ssr');
    if (jssStyles) {
      jssStyles.parentElement.removeChild(jssStyles);
    }
  }, []);

  return(
    <Provider store={store}>
      <ThemeProvider theme={theme}>
        <CssBaseline />
        <BrowserRouter>
          <App />
        </BrowserRouter>
      </ThemeProvider>
    </Provider>
  );

}


hydrate(<Client />,document.getElementById('root'));
Unwarranted answered 23/10, 2020 at 7:0 Comment(2)
I see that you remove the styles you wish to apply in useEffect, have you tried to avoid doing that?Rowley
@Rowley is correct, the way to do this is to use the 3rd parameter of hydrate which is a callback function that is called after hydration is complete. This callback function is where you should be removing no longer needed styles / variables from the DOM. With your current set up your application has yet to hydrate fully before you remove the server side styles, this means not all the styles for the elements have been created and they will be displayed unstyled until hydration is complete and the styles have fully been created by the client side.Lefkowitz
L
2

This is caused by your client side code removing the server generated styling before the client styling has been generated.

The hydrate function provided by react-dom has a 3rd parameter which is a callback function that is called after hydration of the app is complete.

This callback function should be used to ensure that the app has completed hydration and has generated the client styling.

import React,{useEffect} from 'react';
import { hydrate } from 'react-dom';
import {theme} from '../theme';
import { ThemeProvider } from '@material-ui/styles';
import { Provider } from 'react-redux';
import configureStore from '../common/store/configureStore';
import App from '../common/containers/App';
import { BrowserRouter } from "react-router-dom";
import "../common/assets/SCSS/app.scss";
import * as serviceWorker from '../serviceWorker';
import CssBaseline from '@material-ui/core/CssBaseline';

const store = configureStore(window.__PRELOADED_STATE__);

const Client = () => {
  return(
    <Provider store={store}>
      <ThemeProvider theme={theme}>
        <CssBaseline />
        <BrowserRouter>
          <App />
        </BrowserRouter>
      </ThemeProvider>
    </Provider>
  );

}


hydrate(
    <Client />,
    document.getElementById('root'),
    () => {
        const jssStyles = document.querySelector('#jss-ssr');

        if (jssStyles) {
            jssStyles.parentElement.removeChild(jssStyles);
        }
    }
);
Lefkowitz answered 27/10, 2020 at 2:2 Comment(4)
If you use chrome, go into dev tools -> Network -> find the request that serves your html -> click on it and select Preview. does the Preview look as you assume it should (this should be the page from the server with no JS run)?Lefkowitz
In the preview, i could see the unstyled page.for example: h1 tag content is too big.Unwarranted
This usually means that the issue is with getting your styles from the server to the client. As the server should have provided the styles to the head of your html to make the preview look correct.Lefkowitz
Are the styles added by your server side code as you would expect for the page being served?Lefkowitz

© 2022 - 2024 — McMap. All rights reserved.