简体   繁体   中英

React HMR with SSR

I am trying to setup SSR for React-application, when I am starting server at the first time in development environment all works good (server sending browser HTML and CSS), after changing source code of my application I getting an error:

在此处输入图片说明

This error was throwing because on server source code is outdated, but client has a new version and React notify me over this trouble. I think the solve of this trouble is mechanism called HMR (hot module replacement), but setting up this is hard for me.

My server Webpack-config looks like this:

const serverConfig = merge(commonConfig, {
  name: 'server',
  target: 'node',
  externals: [
    nodeExternals({
      whitelist: ['webpack/hot/poll?1000'],
    }),
  ],
  entry: ['webpack/hot/poll?1000', appServerEntry],
  output: {
    path: path.resolve(appDist, 'server'),
    filename: 'index.js',
  },
  plugins: [new webpack.HotModuleReplacementPlugin()],
  resolve: {
    extensions: ['.js', '.jsx', '.json'],
  },
});

On each request server render a new version of UI

app.get('*', (request, response) => {
  const staticAssets = getStaticAssets();
  const routerContext = {};
  const renderStream = renderDocumentToStream({
    staticAssets,
    request,
    routerContext,
  });
  const cacheStream = createCacheStream(request.path);

  response.writeHead(200, { 'Content-Type': 'text/html' });
  response.write('<!DOCTYPE html>');

  cacheStream.pipe(response);
  renderStream.pipe(cacheStream);
});

For the hot reload I use webpackDevMiddleware and webpackHotMiddleware

const webpackInstance = webpack(webpackConfig);
const clientCompiler = webpackInstance.compilers.find(cmpl => cmpl.name === 'client');

app.use(
  webpackDevMiddleware(clientCompiler, {
    hot: true,
    stats: 'errors-only',
  }),
);
app.use(webpackHotMiddleware(clientCompiler));

renderDocumentToStream function intended for render App to NodeStream :

import App from './App';

renderDocumentToStream: ({ request, staticAssets, routerContext }) => {
  const rootMarkup = renderToString(
    <StaticRouter location={request.url} context={routerContext}>
      <App />
    </StaticRouter>
  );

  return renderToNodeStream(
    <Document
      rootMarkup={rootMarkup}
      staticAssets={staticAssets}
    />,
  );
},

if (module.hot) {
  console.log('HERE-0');
  module.hot.accept('./App', () => {
    console.log('HERE-1');
  });
}

When server is starting in stdout logged first call of console.log

在此处输入图片说明

second call of console.log not logged, even after App.jsx has been changed

在此处输入图片说明

What a im doing wrong?

The warning is clearly indicating that the content is different at client from server. This is not an error but a warning which means, there are going to be performance impact because React has to re-render at client and so the whole purpose of having SSR will be defeated. Please check and make sure that client is also getting the same for hydration as the server. This should fix the issue.

I do not think it is possible to solve because the server side will not refresh on change with HMR.

Personally when doing React with SSR I setup nodemon to listen to file changes on the SSR output files and restart the application. Unfortunately this isn't as fast as HMR and you lose current state, but I do not think there is an alternative if you will be using SSR (or ignore the warnings)

This is also the case when using Next.js: https://github.com/zeit/next.js/issues/791

This seemed to fix the client and server mismatch issue during HMR updates for my react-ssr-kit :

 const renderMethod = module.hot ? ReactDOM.render : ReactDOM.hydrate;

My setup is slightly different, but it should still work for yours:

import React from "react";
import ReactDOM from "react-dom";
import { createBrowserHistory } from "history";
import App from "root";
import routes from "routes";
import configureStore from "store/configureStore";

const history = createBrowserHistory(); // create browserhistory
const initialState = window.__INITIAL_PROPS__; // grabs redux state from server on load
const store = configureStore(history, initialState); // sets up redux store with history and initial state

const renderApp = props => {
  const renderMethod = module.hot ? ReactDOM.render : ReactDOM.hydrate; // if module is hot, use ReactDOM's render, else hydrate

  renderMethod(<App {...props} />, document.getElementById("root"));
};

renderApp({ routes, history, store }); // initial App render (ReactDOM.hydrate)

// enable webpack hot module replacement
if (module.hot) {
  module.hot.accept("./routes", () => {
    try {
      const nextRoutes = require("./routes").default;
      renderApp({ routes: nextRoutes, history, store }); // hot-module updates (ReactDOM.render)
    } catch (err) {
      console.error(`Routes hot reloading error: ${err}`);
    }
  });
}

This warning could mean you have a component that needs to render differently on the client side, and you are not making sure your "first-pass" render is the same on the server and the client side.

It is okay for them to differ, for instance, if you load a spinner when there is no data, and this data is not going to be there on the server side, then go ahead and guarantee the spinner always renders on the server and on the "first-pass" render on the browser (the render call before componentDidMount() is fired) are identical.

Then when your data is there trigger a state change to "2nd-pass" render.

Also, one thing I warn against is using the module.hot ? ReactDOM.render : ReactDOM.hydrate module.hot ? ReactDOM.render : ReactDOM.hydrate .

I regretted using this method at work, because it masks hard to detect mismatches in development.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM