Isomorphic JavaScript means that we get to share the same code on the client and on the server. So basically, you are rendering your application markup on the server and piping it down as complete HTML to the browser.
Why Isomorphism?
The reasoning behind an isomrophic app is that instead of bundling all your React
code into a single bundle (and then using something like <script src="bundle.js"> in a bare bones HTML page), you run a Node.js server that serves the files for you.
Rendered in server the app can simply display that rendered string to the user when they first visit the page. When the user visits the page, that rendered string gets overwritten after the bundle is downloaded and then the React app runs like normal.
JavaScript driven MVCs (angular, ember, backbone, etc.) render on DOM load, this can be really slow and can make for a bad user experience. And they are not indexable by search engines.
So the Isomorphic JavaScript will:
- Render HTML from JavaScript app on the server
- Browser loads with full HTML and JavaScript
- Bootstraps the app.
Benefits of Isomorphic JavaScript
- Faster Perceived Load Times
- Better overall user experience
- Search Engine Indexability
- Progressive Enhancements
- Easier Code Maintenance
Installation
Install Node.js (atleast 0.12).
Create a directory for your project and initialise a package.json file by running:
npm init
Now let's install the necessary packages by running the following command:
npm install --save-dev babel babel-loader express jade react react-hot-loader react-router webpack webpack-dev-server nodemon
After the installation, create your directory structure as follows:
mkdir src src/server src/shared src/client views
server
- Holds the backend Node server and Webpack dev serverclient
- Holds the React bundleshared
- Hold your components, flux, and routes
HTML view
Create a file called index.html in the views folder, and paste the following:
<html>
<head>
<title>Isomorphic JavaScript</title>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="description" content=""/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
</head>
<body>
<div id="app">```
</body>
<script src="http://localhost:8080/js/app.js" defer="defer"></script>
</html>
Webpack
Create a new file called webpack.js in src/server/:
import WebpackDevServer from "webpack-dev-server";
import webpack from "webpack";
import config from "../../webpack.config.dev";
var server = new WebpackDevServer(webpack(config), {
// webpack-dev-server options
publicPath: config.output.publicPath,
hot: true,
stats: { colors: true },
});
server.listen(8080, "localhost", function() {});
Create webpack.config.dev.js in the root of your folder:
var webpack = require('webpack');
module.exports = {
devtool: 'inline-source-map',
entry: [
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
'./src/client/entry',
],
output: {
path: __dirname + '/public/js/',
filename: 'app.js',
publicPath: 'http://localhost:8080/js/',
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
],
resolve: {
extensions: ['', '.js']
},
module: {
loaders: [
{
test: /\.jsx?$/,
loaders: ['react-hot', 'babel-loader?experimental'],
exclude: /node_modules/
}
]
}
}
Node.js
Create a new file in src/server called server.js:
import express from "express";
import React from "react";
import Router from "react-router";
const app = express();
import routes from "../shared/routes";
app.get('/*', function (req, res) {
Router.run(routes, req.url, Handler => {
let content = React.renderToString(<Handler />);
res.render('index', { content: content });
});
});
var server = app.listen(3000, function () {
var host = server.address().address;
var port = server.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
Client
Create src/client/entry.js and import react-router:
import React from "react";
import Router from "react-router";
import routes from "../shared/routes";
Router.run(routes, Router.HistoryLocation, (Handler, state) => {
React.render(<Handler />, document.getElementById('app'));
});
Routes
Create routes.js in /src/shared which is the link that is combining our server and client:
import { Route } from "react-router";
import React from "react";
import AppHandler from "./components/AppHandler";
export default (
<Route handler={ AppHandler } path="/" />
);
Running the server
Run following command from root directory:
npm start
This will run a watch task for Babel to handle the es6, webpack-dev-server to hot reloading, and the Node server.
Visit http://localhost:3000, and you should see "Hello App Handler". That's our Isomorphic App!
You can find the source code in Github: Isomorphic JavaScript