mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
93.64k stars 32.22k forks source link

SSR Empty CSS #15018

Closed bryandbor closed 5 years ago

bryandbor commented 5 years ago

I'm creating a new project built on Razzle and I'm attempting to get SSR working correctly. However, despite following the SSR guide and the next SSR example as closely as possible, my SSR styles do not seem to be created at all. To test this, I also added a console.log statement to check what is being returned from my call to sheetsRegistry.toString(), and the value is an empty string. It seems to me as though the StylesProvider component is not utilizing the sheetsRegistry when attempting to render the <App/> and it's Material UI components/styles.

Here are the contents of my files for context: package.json:

{
  "name": "my-razzle-app",
  "version": "0.1.0",
  "license": "MIT",
  "scripts": {
    "start": "razzle start",
    "build": "razzle build",
    "test": "razzle test --env=jsdom",
    "start:prod": "NODE_ENV=production node build/server.js"
  },
  "dependencies": {
    "@fortawesome/fontawesome-svg-core": "^1.2.15",
    "@fortawesome/free-brands-svg-icons": "^5.7.2",
    "@fortawesome/pro-light-svg-icons": "^5.7.2",
    "@fortawesome/pro-regular-svg-icons": "^5.7.2",
    "@fortawesome/pro-solid-svg-icons": "^5.7.2",
    "@fortawesome/react-fontawesome": "^0.1.4",
    "@material-ui/core": "^4.0.0-alpha.4",
    "@material-ui/styles": "^4.0.0-alpha.4",
    "@risingstack/protect": "^1.2.0",
    "body-parser": "^1.18.3",
    "clsx": "^1.0.3",
    "cookie-parser": "^1.4.4",
    "deep-extend": "^0.6.0",
    "express": "^4.16.4",
    "express-session": "^1.15.6",
    "helmet": "^3.16.0",
    "jss": "^10.0.0-alpha.14",
    "moment": "^2.24.0",
    "morgan": "^1.9.1",
    "morgan-json": "^1.1.0",
    "qs": "^6.6.0",
    "razzle": "^2.4.1",
    "razzle-plugin-flow": "^1.0.0",
    "react": "^16.8.4",
    "react-dom": "^16.8.4",
    "react-helmet": "^5.2.0",
    "react-jss": "^8.6.1",
    "react-redux": "^6.0.1",
    "react-router-dom": "^4.4.0",
    "redux": "^4.0.1",
    "redux-actions": "^2.6.5",
    "redux-saga": "^1.0.2",
    "reselect": "^4.0.0",
    "serialize-javascript": "^1.6.1",
    "uuid": "^3.3.2",
    "winston": "^3.2.1",
    "winston-daily-rotate-file": "^3.8.0"
  },
  "devDependencies": {
    "@fortawesome/fontawesome-pro": "^5.7.2",
    "jest": "^24.5.0"
  }
}

render.js (used by express app for rendering nearly all routes)

import React from 'react';
import serialize from 'serialize-javascript';
import {renderToString} from 'react-dom/server';
import {SheetsRegistry} from 'jss';
import {StylesProvider, ThemeProvider, createGenerateClassName} from '@material-ui/styles';
import Button from '@material-ui/core/Button';

import getTheme from '../../styles/theme';
import {configureStore} from '../../redux/store';

import {Provider} from 'react-redux';
import {StaticRouter} from 'react-router-dom';
import App from '../../components/App';

const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);

const theme = getTheme();

export const handler = (req, res) => {
  const sheetsRegistry = new SheetsRegistry();
  const sheetsManager = new Map();
  const generateClassName = createGenerateClassName();

  const context = {};

  const store = configureStore({query: req.query});

  const html = renderToString(
    <StylesProvider generateClassName={generateClassName} sheetsRegistry={sheetsRegistry} sheetsManager={sheetsManager}>
      <ThemeProvider theme={theme}>
        <StaticRouter context={context} location={req.url}>
          <Provider store={store}>
            <App />
          </Provider>
        </StaticRouter>
      </ThemeProvider>
    </StylesProvider>,
  );

  const css = sheetsRegistry.toString();
  // console.log('CSS', css);

  const state = store.getState();

  if (context.url) {
    res.redirect(context.url);
  } else {
    res.status(200).send(renderFullPage({html, css, state}));
  }
};

export const renderFullPage = ({html, css, state}) =>
  `<!doctype html>
  <html lang="">
  <head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta charset="utf-8" />
    <title>Testing</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
    <style id="jss-server-side">${css}</style>
    ${assets.client.css ? `<link rel="stylesheet" href="${assets.client.css}">` : ''}
    ${
      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">${html}</div>
    <script id="server-redux">
      window.__PRELOADED_STATE__ = ${serialize(state)}
    </script>
  </body>
  </html>`;

export default handler;

client.js (Not that it is vital here as the issue is missing CSS from the server)

import React from 'react';
import {hydrate} from 'react-dom';

import {BrowserRouter} from 'react-router-dom';
import {Provider} from 'react-redux';

import {MuiThemeProvider} from '@material-ui/core/styles';
import getTheme from './styles/theme';

import {configureStore} from './redux/store';

import App from './components/App';

const store = configureStore({initialState: window.__PRELOADED_STATE__});

export const Main = () => {
  React.useEffect(() => {
    // Delete server Redux state
    delete window.__PRELOADED_STATE__;
    const serverReduxState = document.querySelector('#server-redux');
    if (serverReduxState) {
      serverReduxState.parentNode.removeChild(serverReduxState);
    }

    // Remove server-side generated styles
    const jssStyles = document.querySelector('#jss-server-side');
    if (jssStyles) {
      jssStyles.parentNode.removeChild(jssStyles);
    }
  }, []);

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

hydrate(
  <MuiThemeProvider theme={getTheme()}>
    <Main />
  </MuiThemeProvider>,
  document.getElementById('root'),
);

if (module.hot) {
  module.hot.accept();
}

Expected Behavior 🤔

Material UI styles/classes will be added server-side to avoid needing to be generated client-side (initially at least).

Current Behavior 😯

If I remove the client-side code which deletes the server-side <style/> tag for clarity, it is clear that it is empty; therefore, the styles are all being generated client-side. This obviously causes the "flicker" that we all want to avoid when using SSR.

<!doctype html>
  <html lang="">
  <head>
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta charset="utf-8" />
  <title>Testing</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
  <style id="jss-server-side"></style>
  <script src="http://localhost:3001/static/js/bundle.js" defer crossorigin></script>
  </head>
  <body>
  <div id="root"><div>Home<button class="MuiButtonBase-root-1ie4scs MuiButton-root-14yc01q MuiButton-outlined-1d36uf3" tabindex="0" type="button"><span class="MuiButton-label-ui4jmt">Testing Button</span></button></div></div>
  <script id="server-redux">
    window.__PRELOADED_STATE__ = {"meta":{}}
  </script>
  </body>
  </html>
<!doctype html>

Steps to Reproduce 🕹

My repo is private; but I forked almost all the same setup into this repo.

Context 🔦

SSR styling is, obviously, very important as I would like to avoid the flicker of unstyled HTML elements on the screen before displaying a beautiful UI.

For the record, I took a look at #9097; however, since this issue is based on Material UI V4, I can't be sure that the issues are related, and there didn't seem to be any SSR issues for V4 to help out either.

Your Environment 🌎

Tech Version
Material-UI ^4.0.0-alpha.4
Material-UI Styles ^4.0.0-alpha.4
Jss ^9.8.7 & tried ^10.0.0-alpha.14
React ^16.8.4
Browser Chrome
TypeScript N/A
oliviertassinari commented 5 years ago

At first sight, I would try upgrading jss to the lastest alpha version. It looks like a duplicate of one of our existing issues.

bryandbor commented 5 years ago

At first sight, I would try upgrading jss to the lastest alpha version. It looks like a duplicate of one of our existing issues.

I know it isn't clear from the repo, but I also had the exact same issue even after upgrading to ^10.0.0-alpha.14.

oliviertassinari commented 5 years ago

@bryandbor Make sure you are using jss@v10.0.0-alpha.14 https://github.com/bryandbor/Material-Razzle-Boiler/blob/master/yarn.lock#L5814. Tested and verified, it's a duplicate of #14887

bryandbor commented 5 years ago

@bryandbor Make sure you are using jss@v10.0.0-alpha.14 https://github.com/bryandbor/Material-Razzle-Boiler/blob/master/yarn.lock#L5814. Tested and verified, it's a duplicate of #14887

Alright. I thought I had tried a clean install of jss@next aka alpha.14, but I can see now that the yarn lock was causing it not to install the latest version. Thanks for the help. 👍