Building a React App in Simpl.js

React.js is a popular javascript library for building dynamic user interfaces using a declarative programming style. In this article we will walk through an implementation of a simple web application—the Blog app—that uses React along with the webapp and html modules as part of a modern and extensible component-based web framework.


The basic web framework described here includes the following features:

  • React components rendered on both the server and client
  • Dependency resolution for components and assets
  • Script and stylesheet delivery with cache optimization

It does not include the following:

  • Single-page app style navigation
    This approach tends to require a lot of additional tooling to maintain client-side state within the app and is usually not needed or desirable.
  • Bundler/transpiler system (e.g., Babel/Webpack)
    Javascript code is delivered to the client via Function.prototype.toString, which is fast and simple, but precludes the use of JSX or newer Javascript syntax not widely supported on browsers. However a jsx helper function keeps React component code clean and concise. CSS is generated using html.css from a JSON data structure in code.

Defining Views

A View consists of a React component factory function and, optionally, a CSS style object, an includes object with external style or script URLs, and an array of other view names as dependencies. In this example, and in the full Blog app implementation, the views are declared in a single object by name:

var views = {
  form: {
    dependencies: ['toggle'],
    includes: {
      styles: [''],
      scripts: ['']
    // becomes /css/form.css:
    style: {
      '.form': {backgroundColor: '#eee'}
    // becomes /js/form.js:
    component: function(site) {
      return function() {
        return ['form', {className: 'form', action: site.url('counter')},
          [site.components.toggle, {name: 'enabled', value: this.props.enabled}],
          ['button', 'Submit']
  toggle: {
    // becomes /js/toggle.js:
    component: function(site) {
      // any code here runs once (server and client)
      return {
        componentDidMount: function() {
          // anything here runs only client-side for each element created
        getInitialState: function() {
          return {checked: this.props.value};
        render: function() {
          var name =,
              checked = this.state.checked;
          return ['input', {type: 'checkbox', name: name, checked: checked, onChange: function(e) {

A view's React component is returned by the component function as a plain object for use with createReactClass. For convenience, a simple render function can also be returned, and the return value of render is itself run through a jsx helper function (described below) to allow for a simple array representation of React elements without a JSX compiler.

To create a React class, component also receives a site instance for access to other components and for constructing site URLs.

Rendering the Page

A site object is constructed on both the server and client with the function below. This object's init method creates the react components from the component factory functions above.

var site = function(components, resolve, paths, title) {
  var jsx = function(node) {
    // node is [type, props, (node...)] or [node] or string
    if (!Array.isArray(node)) return node;
    var type = node[0], props, children;
    if (!type || Array.isArray(type)) {
      type = React.Fragment;
      children = node;
    } else if (typeof node[1] == 'object' && !Array.isArray(node[1])) {
      props = node[1];
      children = node.slice(2);
    } else {
      children = node.slice(1);
    return React.createElement.apply(null, [type, props].concat(;
  var site = {
    components: components,
    url: function(name, args) {
      return resolve(name, args, {paths: paths});
    init: function(root, props) {
      var createClass = React.createClass || createReactClass;
      Object.keys(components).forEach(function(name) {
        var component = components[name](site),
            render = component.render;
        if (typeof component == 'function') {
          render = component;
          component = {};
        component.render = function() {
          return jsx(;
        components[name] = createClass(component);
      if (root) { // runs on client only
        props.title = title; // add static sitewide props from config
        ReactDOM.hydrate(React.createElement(, props), document.getElementById(root));
  return site;

On the server, site is initialized once with all of the app's React components:

var components = Object.keys(views).reduce(function(components, name) {
  components[name] = views[name].component;
  return components;
}, {});

site(components, app.url, app.paths).init();

Meanwhile, res.render generates an HTML page with a view component and props using renderToString. To hydrate this markup on the client, the page includes the following scripts, in order:

  • /js/site.js - the site object constructor, instantiated as Site
  • /js/:view.js - the component factory script for the view specified in the call to res.render and for each view in its graph of dependencies, which populates Site.components with the views needed on that page
  • An inline script calling Site.init with serialized page props, which creates React classes from Site.components and calls ReactDOM.hydrate

Delivering Static Files

Stylesheets and scripts are delivered to the client from the app endpoints below. See the webapp module for details on web app routing.

app.get('/js/:view.js', function(req, res) {
  var name = req.params.view,
      // site.js is a special case
      fn = name == 'site' ? site : (views[name] || {}).component;
  if (!fn) return res.generic(404);
  var js = script(fn, name);
  if (md5(js) != req.query.v) return res.generic(404);
  res.end(js, {
    'Content-Type': 'text/javascript',
    'Cache-Control': 'public, max-age=3600'
}, 'script');

app.get('/css/:view.css', function(req, res) {
  var view = views[req.params.view] || {};
  if (! return res.generic(404);
  var css = html.css(;
  if (md5(css) != req.query.v) return res.generic(404);
  res.end(css, {
    'Content-Type': 'text/css',
    'Cache-Control': 'public, max-age=3600'
}, 'stylesheet');

When called, res.render collects the needed style objects, in addition to the component functions as described earlier, of a given top-level view to generate the script and stylesheet URLs to include in the rendered page markup. A Cache-Control header in the response, along with a content hash value in the URL, allow the client to cache these resources while fetching new ones as soon as they are changed.