Skip to content

Instantly share code, notes, and snippets.

@matthewoden
Created June 24, 2015 00:37
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matthewoden/0ef78e5dc6ee04b6aa1e to your computer and use it in GitHub Desktop.
Save matthewoden/0ef78e5dc6ee04b6aa1e to your computer and use it in GitHub Desktop.
How to set up server side rendering with react proxy loader, and avoid a checksum mismatch.
/*
So the above file here is my Full page react file. I do a couple things here:
- In the head, I inject my important CSS inline.
- In the body, I link to my commons chunk (with react, react-router, and anything that's going to be on every single page).
- Beneath that is the app entry file.
- Beneath that is a dynamically generated chunkfile, based on the current route.
Each of these are cachebusted with a hash, pulled from the webpack stats file.
*/
class FullPage extends React.Component {
render() {
return (
<html>
<head>
<meta charSet="utf-8"/>
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<title>{this.props.title}</title>
<meta name="description" content=""/>
<meta name="viewport" content="width=device-width"/>
<style dangerouslySetInnerHTML={{__html: this.props.css}}/>
</head>
<body>
<div id="app" dangerouslySetInnerHTML={{__html: this.props.markup}}/>
<script src={"/assets/"+this.props.hash+".common.js"}></script>
<script src={"/assets/"+this.props.hash+".app.js"}></script>
<script src={"/assets/"+this.props.hash+"."+this.props.chunk+".js"}></script>
</body>
</html>
);
}
}
/*
This is my route file.
You can see here that I've put a proxy loader on the top level routes,
and set the chunk name to match the path
*/
'use strict';
import React from 'react';
import { Route, DefaultRoute, NotFoundRoute } from 'react-router';
//Components
var App = require('./components/app/');
var Home = require('react-proxy-loader?name=home!./components/home/');
var PageOne = require('react-proxy-loader?name=pageone!./components/pageOne/');
var PageTwo = require('react-proxy-loader?name=pagetwo!./components/pageTwo/');
export default (
<Route handler={ App } path="/" >
<DefaultRoute handler={ Home } />
<Route path="/pageone" handler={ PageOne } />
<Route path="/pagetwo" handler={ PageTwo } />
<NotFoundRoute handler={ Home } />
</Route>
);
/**
This is my server side router. A little messy, but as far as I can tell, a fairly typical setup:
- grab fullpage component
- grab the handler component from react-router
- render to string, static markup. Pass the props in.
The chunk prop, in order to be dynamic, just passes in a formatted string based on the route.
**/
'use strict';
//React
import fs from 'fs';
import React from 'react';
import Router from 'react-router';
import DocumentTitle from 'react-document-title';
import Routes from '../assets/prerender/app';
import Html from '../client/components/fullPage/';
import Stats from '../client/stats.json';
// TODO Add in configurable for page types, pass that into html.
// Like app.use, but switching based on route param.
let css;
if(!css){
fs.readFile('./assets/public/' + Stats.hash + '.common.css', 'utf8', function (err,data) {
if (err) {
return console.log(err);
}
css = data;
});
}
module.exports = function(req, res, next) {
Router.run(Routes, req.url, function (Handler, state) {
var chunk = req.url.replace('/','');
var title = DocumentTitle.rewind();
var markup = React.renderToString(<Handler />);
var html = React.renderToStaticMarkup(<Html
title={title}
hash={Stats.hash}
css={css}
chunk={chunk}
markup={markup} />);
res.send('<!DOCTYPE html>' + html);
});
};
/*
This is my webpack output configuration. I keep each webpack property separated out into separate modules.
Webpack configs can get kind of hairy, so I keep them all separate.
*/
'use strict';
var path = require('path');
module.exports = {
client:{
path: path.resolve(__dirname, '../assets/public'),
publicPath:'/assets/',
filename: '[hash].[name].js',
chunkFilename:'[hash].[name].js'
},
server:{
path: path.resolve(__dirname, '../assets/prerender'),
publicPath:'/assets/',
filename: '[name].js',
libraryTarget: 'commonjs2'
},
};
@jamesjjk
Copy link

Hey, having a look at your code here. Did you setup static routes file for server side rendering, it seems server side is pointing at a pre-rendered app, how did you do the prerender properly? Care to share?

@jamesjjk
Copy link

I checked your above code, but you still get a checksum error. Any idea how you circumvented this?

@matthewoden
Copy link
Author

@jamesjjk Oh man, I totally missed this. I'm months late, too. Ugh. Sorry.

So I ended up fielding this question elsewhere too. Most often, the checksum error is a result of a problem with React's context api (at least, at the time I wrote this gist - so react 0.13? I think?). When prerendering, webpack likes to bundle in all the dependancies it can. This is great for the browser, that doesn't have access to file directories like your node server. But your node server doesn't need a bunch of duplicates modules. It can just grab from the node_modules folder.

If you don't mark react as external in your server side config, you end up with two different copies of react. And two different context trees, which results in the checksum mismatch.

Your configuration may need something different, but for externals, my server side webpack config looks something like this:

{
// ...the rest of your server webpack config here
externals:[/^[a-z\-0-9]+$/]
}

Which basically looks at a require, and checks if it's using a relative path or not.

... I should really update this gist to use modern formating and React-Router 1.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment