React has been a major actor in the new trending. Its open-source component-based environment made web development far easier and pleasuring. Javascript With its concept, we are able to create powerful web running on the old buddy JS. apps However, it came with some downsides, and one of them relates to the size of the applications (the library itself weighs 45 kb). A new trending of developing new lightweight libraries has come, and with them Inferno: https://www.infernojs.org/ And what’s cool: Inferno is React-like. So, if you love working with React, Inferno will definitely not be a big issue for you. In this page I will show this, with a small sample of server-side rendering written in React; and the very same sample written in Inferno. I’m also using Redux, even if I don’t really need it here, I used it just for fun :) The code consists of a dummy page which displays a list of music albums retrieved from a backend API. The complete code (in React and Inferno), as well as the backend server, can be found here: https://github.com/morris-ribs/server-side-rendering Let us cover three parts: the server rendering, the entrypoint, and the components. The code is based in http://redux.js.org/docs/recipes/ServerRendering.html server rendering The In the entrypoint, we add the code that renders the app in server side and send it to the client. Here is the code with React: // server.js import express from 'express';import routes from '../src/routes';import configureStore from '../src/store/configureStore';import {getDataSuccess} from '../src/actions/discActions';import {fetchData} from '../src/api/DiscApiClient'; import React from 'react';import { renderToString } from 'react-dom/server';import {Provider} from 'react-redux';import { match, RouterContext } from 'react-router'; ...const port = 9800;const app = express(); // This is fired every time the server side receives a requestapp.use(handleRender); function handleRender(req, res) {** 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) { fetchData().then(discs => {// Compile an initial statelet preloadedState = {}; // Create a new Redux store instance const store = configureStore(preloadedState); store.dispatch(getDataSuccess(discs)); // You can also check renderProps.components or renderProps.routes for// your "not found" component or route respectively, and send a 404 as// below, if you're using a catch-all route.** const html = renderToString(<Provider store={store}><RouterContext {...renderProps} /></Provider>);** // Grab the initial state from our Redux storeconst finalState = store.getState(); // Send the rendered page back to the clientres.status(200).send(renderFullPage(html, finalState));}).catch(error => {res.status(500).send(error.message);});} else {res.status(404).send('Not found');}});} // the contents to be rendered on server-sidefunction renderFullPage(html, preloadedState) {return `<!DOCTYPE html><html lang="en"><head><title>Discs Test</title><meta name="viewport" content="width=device-width, initial-scale=1"><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><link href=" " rel="stylesheet"></head><body><div id="app">${html}</div><script>// WARNING: See the following for Security isues with this approach:// window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState)}</script><script src="/bundle.js"></script></body></html>`;} https://fonts.googleapis.com/css?family=Roboto http://redux.js.org/docs/recipes/ServerRendering.html#security-considerations // listen in the port 9800app.listen(port, function(err) {if (err) {console.log(err);} else {open(` );}}); http://localhost:${port}` And now, the code in Inferno (using , and ). inferno-server inferno-router inferno-redux Here the main difference is that we call the function, getting the object from it, and then use it in our : match renderProps RouterContext // server.js import routes from '../src/routes';import configureStore from '../src/store/configureStore';import {getDataSuccess} from '../src/actions/discActions';import {fetchDara} from '../src/api/DiscApiClient'; import Inferno from 'inferno'; import { renderToString } from 'inferno-server';import {Provider} from 'inferno-redux';import { match, RouterContext } from 'inferno-router'; const port = 9800;const app = express(); // This is fired every time the server side receives a requestapp.use(handleRender); function handleRender(req, res) { if (renderProps.redirect) {res.redirect(renderProps.redirect);} else if (renderProps) { const renderProps = match(routes, req.originalUrl); fetchData().then(discs => { // Compile an initial state let preloadedState = {}; // Create a new Redux store instance const store = configureStore(preloadedState); store.dispatch(getDataSuccess(discs)); // You can also check renderProps.components or renderProps.routes for // your "not found" component or route respectively, and send a 404 as // below, if you're using a catch-all route. **const html = renderToString(<Provider store={store}><RouterContext {...renderProps} /></Provider>);** // Grab the initial state from our Redux storeconst finalState = store.getState(); // Send the rendered page back to the clientres.status(200).send(renderFullPage(html, finalState));}).catch(error => {res.status(500).send(error.message);});} else {res.status(404).send('Not found');}} function renderFullPage(html, preloadedState) {return `<!DOCTYPE html><html lang="en"><head><title>Discs Test</title><meta name="viewport" content="width=device-width, initial-scale=1"><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><link href=" " rel="stylesheet"></head><body><div id="app">${html}</div><script>// WARNING: See the following for Security isues with this approach:// window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState)}</script><script src="/bundle.js"></script></body></html>`;} // Exactly like in React! https://fonts.googleapis.com/css?family=Roboto http://redux.js.org/docs/recipes/ServerRendering.html#security-considerations app.listen(port, function(err) {if (err) {console.log(err);} else {open(` );}}); // Exactly like in React! http://localhost:${port}` The entrypoint Here is where we are going to setup Redux store and provider In React, it is like this: // src/index.js import configureStore from './store/configureStore';import routes from './routes';import {loadData} from './actions/discActions'; import React from 'react';import {render} from 'react-dom';import {Provider} from 'react-redux';import {Router, browserHistory} from 'react-router'; // Grab the state from a global variable injected into the server-generated HTMLconst preloadedState = window.__PRELOADED_STATE__; const store = configureStore(preloadedState);store.dispatch(loadDiscs()); render( ,document.getElementById('app')); <Provider store={store}><Router history={browserHistory} routes={routes} /></Provider> In Inferno, we will make some changes, such as passing the main component in the router: // src/index.js import {loadData} from './actions/discActions';import App from './components/App';import DiscPage from './components/disc/DiscPage'; import Inferno from 'inferno';import configureStore from './store/configureStore';import { createBrowserHistory } from 'history';import {Provider} from 'inferno-redux';import {Router, Route, IndexRoute} from 'inferno-router'; // in Inferno, we need to create the browser history from another library const browserHistory = createBrowserHistory(); // Grab the state from a global variable injected into the server-generated HTMLconst preloadedState = window.__PRELOADED_STATE__; const store = configureStore(preloadedState);store.dispatch(loadData()); // the main App component goes as a param to Router// The component page goes into the .render( ,document.getElementById('app')); Inferno <Provider store={store}><Router history={browserHistory} component={ App }><IndexRoute component={DiscPage} /></Router></Provider> The album display component Finally, the component which represents the display of the list of albums on our page. In React I wrote it like: // App.js // This component handles the App template used on every page import React, {PropTypes} from 'react'; class App extends {render() {return(<div>{this.props.children}</div>);}} React.Component App.propTypes = {children: PropTypes.object.isRequired}; export default App; import {connect} from 'react-redux';import Disc from './DiscComponent'; // DiscPage.js /* eslint-disable no-console */class DiscPage extends **React.**Component {constructor(props, context) {super(props, context); this.state = {discs: Object.assign({}, this.props.discs)};} render() { const discsToDisplay = (this.props.discs.albums) ? this.props.discs.albums : \[\]; return ( <div> <Disc discs={discsToDisplay} /> </div> ); } } DiscPage.propTypes = {discs: PropTypes.object.isRequired}; function mapStateToProps(state) {return {discs: state.discs};} const connectedStateAndProps = connect(mapStateToProps); export default connectedStateAndProps(DiscPage); class DiscComponent extends **React.**Component {constructor(props, context) {super(props, context);} // DiscComponent.js render() {return (<div><ul>{this.props.discs.map(disc =><li key={disc.id}>{disc.title} - {disc.artist} ({disc.year})</li>)}</ul></div>);}} DiscComponent.propTypes = {discs: PropTypes.array.isRequired}; export default DiscComponent; Here in Inferno. You can see that we don’t need to declare the Prop Types of the component. In addition to that, the class come in another library, : Component inferno-component // This component handles the App template used on every page // App.js import Inferno from 'inferno';import Component from 'inferno-component'; class App extends {render() {return(<div>{ this.props.children }</div>);}} Component export default App; import Disc from './DiscComponent'; // DiscPage.js import {connect} from 'inferno-redux'; class DiscPage extends {render() {const discsToDisplay = (this.props.discs.albums) ? this.props.discs.albums : [];return (<div><Disc discs={discsToDisplay} /></div>);}} Component function mapStateToProps(state) {return {discs: state.discs};} // with , we can connect components to the immutable store just like in Reactconst connectedStateAndProps = connect(mapStateToProps); inferno-redux export default connectedStateAndProps(DiscPage); class DiscComponent extends {constructor(props, context) {super(props, context);} // DiscComponent.js - just like in React Component render() {return (<div><ul>{this.props.discs.map(disc =><li key={disc.id}>{disc.title} - {disc.artist} ({disc.year})</li>)}</ul></div>);}} export default DiscComponent; And now we compare the size As you can see, writing an app using server-side rendering and Redux is not that different from React and Inferno. So, let’s see if there is really a difference regarding the sizes of the bundled JS between the two libraries, together with the corresponding ones for routing, redux and server-rendering. ( ) Note: I’m using version 15.0.2 of React React (v 15.0.2): 308 Kb Inferno (v 1.0): 170 Kb Conclusion Inferno brings a powerful and light library that is really friendly for developers who are used to work in React. It brings all the best from React ecosystem, with the probability of using the most advanced features of it. It is really a good choice for web component-based apps in a light way (it weighs only 7 kb). React team is already working hard to leverage the size and improve performance of the library. When it comes, it is really worth it to take a look on it. Interesting links on both libraries (and more!): React Inferno Introduction To Functional Front-Ends With Inferno — Side Effects And Routing React and Redux Single Page Applications Resources