A simple isomorphic javascript blog with React and Nodejs
After reading a lot of articles about isomorphic javascript and testing the various examples, I decided to create a simple isomorphic app to better understand the development process.
I’ve created a minimal blog with fake articles wrote using markdown syntax. All the code can be found here.
Iso-wat?
I will not deeply explain what’s a isomorphic app and why it can be very cool. In a few words: > JavaScript applications which run both client-side and server-side.
The app is rendered in an “old-school way” by the server and from that point it begins acting like a SPA. If javascript is turned off, you can navigate the website like a static one, for sure with less cool features, but at least you’ve got some SEO content! And not a white page.
For understand better read this two articles:
Techs
A short list of the frameworks/libraries involved in the project.
React components and Flux architecture
The app (try to) follow the React/Flux architecture. So all the code is divided into: actions, dispatcher, stores and views.
Maybe I didn’t always follow the pattern so strictly, as you will see after with stores, but I did my best to be clear.
Routing
### Client side The client javascript application use react-router as router component. I discover in it a very useful tool, easy and fast to use.
From routes.jsx:
// The handlers are all React views
var Route = Router.Route;
var routes = (
<Route name="app" path="/" handler={App}>
<Route name="article" path="/article/:id" handler={Article}/>
<DefaultRoute name="default" handler={ArticleList}/>
</Route>
);
Put the <RouteHandler />
component in the App view (App.jsx):
var App = React.createClass({
render: function () {
return (
<div>
<header className={"cf"}>
<h1>
Isomorphic
</h1>
<nav>
<ul>
<li><Link to="app">Home</Link></li>
<li><Link to="article" params=>About</Link></li>
</ul>
</nav>
</header>
<section className={"content markdown-body"}>
// Here!!
<RouteHandler/>
</section>
</div>
);
}
});
And finally start the router from the browser entrypoint (browser.js):
Router.run(routes, Router.HistoryLocation, function (Handler, state) {
React.render(<Handler/>, document.body);
var activeRoute = RoutesAction.findActiveRoute(state.routes);
// Every time there's a route change
// ask RoutesAction to manage the flow
RoutesAction.triggerRouteChange(activeRoute, state.params);
});
Server side
The server is built with Express and use its routing system as a wrapper for the client app routing. Every time a server side route it’s called, the callback call the relative client route and using React.renderToString
return the rendered html to a Jade view.
As in server.js :
app.get('/article/:id', function (req, res, next) {
var aid = req.params.id;
Router.run(routes, '/article/' + aid , function (Handler) {
var content = React.renderToString(React.createElement(Handler));
var injected = { list: [Api.getArticle(aid)]};
res.render('index', {
content: content,
injectedScript: JSON.stringify(injected)
});
});
});
With the html I inject also some javascript data with injectedScript
.
I do this to give React the initial data and prevent a re-render to a white page. When started on the client, the app gets the data from a client api so, if the React diff algorithm find something different, it will cause a re-render.
This is better explained in the next section.
Stores and Api
All the views get their data from a store.
Here there’s the small trick who will help us do the magic.
ArticleStore.js :
// This Api is override on browser
// using 'browser' field in package.json
var Api = require('../api/ServerApi.js');
var _data = Api.getArticles();
function loadData(data) {
_data = data;
};
var ArticleStore = merge({}, EventEmitter.prototype, {
findById: function (id) {
return Api.getArticle(id);
},
getData: function () {
return _data;
},
// ...
});
// ...
ServerApi.js :
module.exports = {
getArticles: function () {
var articles = fs.readdirSync(FILES_DIR),
list = [];
articles.forEach( function (a) {
list.push(/*...*/);
});
return list;
},
//...
}
ClientApi.js :
function getInjectedData(key) {
var inj = window.__INJECTED[key];
return inj;
};
var _data = getInjectedData("list") || [];
module.exports = {
getArticles: function () {
return _data;
},
//...
}
As you could see, the api implements the same “sort of” interface. The override of the module is done in package.json:
"browser": {
"./src/api/ServerApi.js": "./src/api/ClientApi.js",
"showdown": "./vendor/showdown/showdown.js"
},
"browserify": {
"transform": [
"reactify"
]
},
Ok, but in real world?
This was simply test, like the usual todo apps. All the code can be found here.
Far from being perfect, I hope soon to get the possibility to try these techs on a real world app or project to face real world problems.
If you want something more prod-ready or a list of various isomorphic frameworks, visit http://isomorphic.net/.
Hope you’ll enjoy. Ciao!