Joshua
Joshua

Reputation: 3166

React server side rendering renders incorrect content

I'm working on a react web app using React, Redux, and React Router, with server side rendering (using express)

The problem I'm facing is a bit hard to explain. I will try to explain it in the following steps.

  1. You first enter the app from a URL like http://www.example.com/articles/1234. The express server will send down the correct content which includes the correct page source and the DOM(from chrome Element panel) as the initial load.

  2. Then let's navigate to a different page like http://www.example.com/articles/5678 (navigating is not causing page refresh since it's a single page app, it only reaches out to the server for SSR content when you do a refresh with your browser or entering a page from a URL in browser address bar) everything works fine so far.

  3. Refresh the page in the browser (You're now on page http://www.example.com/articles/5678) with cmd + r or F5. Again, the server will send down the content. But this time the content that you're receiving is a bit different. The page content in the browser is correct as well as DOM from chrome Elements panel. However, the page source is not correct, it's still the old page source that you got from step 1.

I have tried to log out the content when refreshing the page. The content that the server sent down in step 3 is not the correct content (It's the previous page) but somehow the browser can still see the right content.

And if I refresh the page once more after step 3 then I will get both the correct content and correct page source...

I'm also using Facebook open graph debugger. The debugger is telling me that it follows a redirect to step 3. And the redirect URL it followed is the previous page URL. I know this not quite right, but not sure where I'm doing it wrong...

Here are my express server settings

Thanks for the help!

server.js

require('babel-register');
const express = require('express');
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const ReactRouter = require('react-router');
const ReactRedux = require('react-redux');
const Store = require('./src/store/configureStore').default;
const routes = require('./src/routes').default;
const compression = require('compression');
const morgan = require('morgan');

const store = Store();
const match = ReactRouter.match;
const RouterContext = ReactRouter.RouterContext;
const Provider = ReactRedux.Provider;
const port = process.env.PORT || 5050;
const ReactHelmet = require('react-helmet');

const Helmet = ReactHelmet.Helmet;

const app = express();
app.use('/dist', express.static('./dist'));

app.set('view engine', 'ejs');

app.use(compression());
app.use(morgan('combined'));

app.use((req, res) => {
  // prettier-ignore
  match(
    { routes, location: req.url },
    (error, redirectLocation, renderProps) => {
      if (error) {
        res.status(500).send(error.message);
      } else if (redirectLocation) {
        res.redirect(302, redirectLocation.pathname + redirectLocation.search);
      } else if (renderProps) {
        console.log(renderProps);
        const body = ReactDOMServer.renderToString(
          React.createElement(
            Provider,
            { store },
            React.createElement(RouterContext, renderProps)
          )
        );
        const meta = Helmet.renderStatic().meta.toString();
        res.status(200).render('index', {body, meta});
      } else {
        res.status(404).send('Not found');
      }
    }
  );
});

console.log('listening on port ' + port); // eslint-disable-line

app.listen(port);

Upvotes: 1

Views: 1364

Answers (1)

Joshua
Joshua

Reputation: 3166

I have eventually resolved the issue.

The main problem was that I placed my async data fetching calls in the componentWillMount lifecycle method instead of componentDidMount.

And I didn't properly handle async data fetching in my App.

I'm now using the approach mentioned in this article to deal with my async data fetching issue.

Basically, I'm doing the following:

  1. On the client side: Create a static method fetchData in components.

  2. On the server side: Use React Router to grab the right component and use the fetchData method to fetch the data needed for that component.

  3. After these promises have been resolved, get the current state from Redux store and render the HTML and send it down to the client.

  4. Inject the state to the client side Redux store and render the page.

Upvotes: 1

Related Questions