erichardson30
erichardson30

Reputation: 5054

React checksum invalid server rendering

In my react app I am currently using server side rendering. The error I am currently getting is :

Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
 (client) <noscript data-reacti
 (server) <div data-reactid=".1

Server.js :

 import 'babel-polyfill';
 import path from 'path';
 import express from 'express';
 import React from 'react';
 import ReactDOM from 'react-dom/server';
 import { match, RouterContext } from 'react-router';
 import assets from './assets';
 import { port } from './config';
 import routes from './routes';
 import ContextHolder from './core/ContextHolder';
 import Html from './components/Html';

 const server = global.server = express();

 //
 // Register Node.js middleware
 // -----------------------------------------------------------------------------
 server.use(express.static(path.join(__dirname, 'public')));

 //
 // Register API middleware
 // -----------------------------------------------------------------------------
 server.use('/api/content', require('./api/content').default);

 //
 // Register server-side rendering middleware
 // -----------------------------------------------------------------------------
 server.get('*', async (req, res, next) => {
   try {
     match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
       if (error) {
         throw error;
       }
       if (redirectLocation) {
         const redirectPath = `${ redirectLocation.pathname }${ redirectLocation.search }`;
         res.redirect(302, redirectPath);
         return;
       }
       let statusCode = 200;
       const data = { title: '', description: '', css: '', body: '', entry: assets.main.js };
       const css = [];
       const context = {
         insertCss: styles => css.push(styles._getCss()),
         onSetTitle: value => data.title = value,
         onSetMeta: (key, value) => data[key] = value,
         onPageNotFound: () => statusCode = 404,
       };
       data.body = ReactDOM.renderToString(
         <ContextHolder context={context}>
           <RouterContext {...renderProps}/>
         </ContextHolder>
       );
       data.css = css.join('');
       const html = ReactDOM.renderToStaticMarkup(<Html {...data} />);
       res.status(statusCode).send(`<!doctype html>\n${html}`);
     });
   } catch (err) {
     next(err);
   }
 });

 //
 // Launch the server
 // -----------------------------------------------------------------------------
 server.listen(port, () => {
   /* eslint-disable no-console */
   console.log(`The server is running at http://localhost:${port}/`);
 });

client.js :

import 'babel-polyfill';
import React from 'react';
import { match, Router } from 'react-router';
import { render } from 'react-dom';
import FastClick from 'fastclick';
import routes from './routes';
import Location from './core/Location';
import ContextHolder from './core/ContextHolder';
import { addEventListener, removeEventListener } from './core/DOMUtils';

let cssContainer = document.getElementById('css');
const appContainer = document.getElementById('app');
const context = {
  insertCss: styles => styles._insertCss(),
  onSetTitle: value => document.title = value,
  onSetMeta: (name, content) => {
    // Remove and create a new <meta /> tag in order to make it work
    // with bookmarks in Safari
    const elements = document.getElementsByTagName('meta');
    [].slice.call(elements).forEach((element) => {
      if (element.getAttribute('name') === name) {
        element.parentNode.removeChild(element);
      }
    });
    const meta = document.createElement('meta');
    meta.setAttribute('name', name);
    meta.setAttribute('content', content);
    document.getElementsByTagName('head')[0].appendChild(meta);
  },
};

function run() {
  const scrollOffsets = new Map();
  let currentScrollOffset = null;

  // Make taps on links and buttons work fast on mobiles
  FastClick.attach(document.body);

  const unlisten = Location.listen(location => {
    const locationId = location.pathname + location.search;
    if (!scrollOffsets.get(locationId)) {
      scrollOffsets.set(locationId, Object.create(null));
    }
    currentScrollOffset = scrollOffsets.get(locationId);
    // Restore the scroll position if it was saved
    if (currentScrollOffset.scrollY !== undefined) {
      window.scrollTo(currentScrollOffset.scrollX, currentScrollOffset.scrollY);
    } else {
      window.scrollTo(0, 0);
    }
  });

  const { pathname, search, hash } = window.location;
  const location = `${pathname}${search}${hash}`;

  match({ routes, location }, (error, redirectLocation, renderProps) => {
    render(
      <ContextHolder context={context}>
        <Router {...renderProps} children={routes} history={Location} />
      </ContextHolder>,
      appContainer
    );
    // Remove the pre-rendered CSS because it's no longer used
    // after the React app is launched
    if (cssContainer) {
      cssContainer.parentNode.removeChild(cssContainer);
      cssContainer = null;
    }
  });

  // Save the page scroll position
  const supportPageOffset = window.pageXOffset !== undefined;
  const isCSS1Compat = ((document.compatMode || '') === 'CSS1Compat');
  const setPageOffset = () => {
    if (supportPageOffset) {
      currentScrollOffset.scrollX = window.pageXOffset;
      currentScrollOffset.scrollY = window.pageYOffset;
    } else {
      currentScrollOffset.scrollX = isCSS1Compat ?
        document.documentElement.scrollLeft : document.body.scrollLeft;
      currentScrollOffset.scrollY = isCSS1Compat ?
        document.documentElement.scrollTop : document.body.scrollTop;
    }
  };

  addEventListener(window, 'scroll', setPageOffset);
  addEventListener(window, 'pagehide', () => {
    removeEventListener(window, 'scroll', setPageOffset);
    unlisten();
  });
}

// Run the application when both DOM is ready and page content is loaded
if (['complete', 'loaded', 'interactive'].includes(document.readyState) && document.body) {
  run();
} else {
  document.addEventListener('DOMContentLoaded', run, false);
}

This is the first time I have explored server side rendering. I know this means the server and client are rending 2 different things so the Client has to re-render anything. It's not breaking anything but I would like to know how to fix this so the warning can go away.

Upvotes: 1

Views: 614

Answers (1)

Kevin Ghadyani
Kevin Ghadyani

Reputation: 7307

This issue is with asynchronous routes. I've found no other solution than to change all routes to be synchronized. The <noscript> tag that's being added in by the client is placed there when loading the async routes.

Example, change:

const routes = {
    path: '/',
    getComponent: function(location, cb) {
        require.ensure([], function(require) {
            return cb(null, require('./views/homepage'));
        })
    };
};

Into this:

const routes = {
    path: '/',
    getComponent: function(location, cb) {
        return cb(null, require('./views/homepage'));
    };
};

EDIT: To re-enable async routes, do this in your client router:

match({ history, routes }, (error, redirectLocation, renderProps) => {
    render(<Router {...renderProps} />, mountNode)
})

Answer edit thanks to @firasd here: Async Routes Causes Server-Side Checksum Invalid Error

Upvotes: 1

Related Questions