Rendering ReactJS templates server-side.

The last couple of months I've been working with ReactJS quite extensively. It's been a very rewarding and insightful journey. There is, however, one part that kept coming back to me: server-side rendering. How on earth am I going to use ReactJS when I want to render my templates on the server? So, I sat down and looked at the possibilities.

After some research, two possible techniques presented itself:

Both of these approaches present their own set of pros and cons. On my particular quest, I first tried going with the V8 bindings provided by V8Js.

Why render server-side?

Before we go into the "how", the "why" is equally interesting.

Rendering your templates server-side has some obvious benefits. Since the contents of the page is there in the initial response, there's less page build-up. This will give your application a more snappy feel to it. It also ensures your pages can be correctly indexed by search engines. An additional bonus is presented in the way that React handles your pre-rendered page.

When React templates are rendered server side, a checksum is added. This hash is checked by the front-end ensuring the UI is in the correct state, and if so, that representation of the DOM is accepted, preventing any further rendering on the client. This, once again, creates a smoother experience in terms of performance.

Enough theory, let's look at some rendering options. Starting with V8Js.

What is V8js?

The V8Js PECL package is a PHP extension which provides V8 bindings for libv8. In order to get this to work you'll have to install libv8 on your machine "somehow". During my research I found that there wasn't a perfect way to install this without reaching for some devops black magic voodoo. After some trial and error, StackOverflow searching, and many libv8 installations, I finally had libv8 installed on my vagrant box (ubuntu). After also installing the PECL extension itself I was able to get to work.

V8Js workflow

A basic workflow using V8Js will look something like this:

  1. Create your ReactJS "views".
  2. Create a build file containing all the views needed for a given template. *
  3. Use the V8Js class, or the reactjs/react-php-v8js composer package to render your views.
  4. Output the contents to the client.

* When using ES6 you'll have to compile down to ES5 also.

Since the people over at Facebook created the reactjs/react-php-v8js package, they obviously want us to use that, and so it shall be done.

$ composer init
$ composer require reactjs/react-php-v8js

And for the most minimal of MVP's in PHP:

<?php
$react = file_get_contents('./js/dist/react.js');
$component = file_get_contents('./js/dist/hello.js');
$rjs = new ReactJS($react, $component);
$rjs->setComponent('HelloComponent', ['name' => 'World!']);
?>
<div id="hello"><?=$rjs->getMarkup();?></div>
<script type="text/javascript" src="/assets/dist/hello.react.bundle.js></script>
<script type="text/javascript"><?=$rjs->getJS("#hello");?></script>

The output this generates will be the rendered view with the corresponding javascript code to pick up where server rendering left us off. While this is perfectly fine for basic usage of React, there are some cases where this just isn't the best fit.

If you're using react-router (a routing component, obviously), the initial rendering is done in a callback triggered by the router. Since the ReactJS class has rendering call hardcoded into the class we're restricted from using one of the two. This can easily be solved by re-implementing the ReactJS class ourselves and facilitating the react-router render callback (which isn't hard) but it does add a level of complexity to our setup.

My thoughts on using V8Js

Unless you're comfortable installing (and updating) libv8 and the V8Js PECL extension on your production machines, this is not an option. Personally, I wouldn't go this route. Installing the dependencies is cumbersome, dependency management is tricky, and there aren't many resources to guide you along the way. In addition, you'll need to account for the fact that your javascript builds should not be bundled with react if you want to re-use them.

ReactJS as a Service

The alternative is running a small node application to render your views, ReactJS as a Service if you will. SPOILER: this is the method I went with, therefor I'll explain how to set this up too.

Render service workflow

  1. Gather data for the request
  2. Offload rendering to node application.
  3. Inject rendered html into a template.

While this sound a lot simpler than the other route, there are some additional steps we need to take on forehand:

  • Build our templates in react.
  • Run the node application.

Getting setup

The first step would be to get node installed on our machine. I'm running ubuntu, but there are instructions for every kind of OS.

$ curl -sL https://deb.nodesource.com/setup | sudo bash -
$ sudo apt-get install -y nodejs

This will install node and npm on your machine. We'll create our project in the ~/Sites/react-and-php directory.

cd ~/Sites
mkdir react-and-php
cd react-and-php

The PHP framework weapon of choice will be Lumen. Needless to say this technique is not tied to any framework in particular, so choose anything you like.

Let's install that using composer's create-project command:

composer create-project laravel/lumen --prefer-dist ./

This command will install Lumen in the our current path. Since our PHP application will be talking over HTTP to our template rendering service we'll include Guzzle too.

composer require guzzlehttp/guzzle

Using npm we'll initiate the node project and install the required dependencies.

In the project root

$ npm init --yes

After entering author details you shouldn't be bothered by any prompts. We can now install the required dependencies. The generated package.json file will look something like this:

{
  "name": "react-and-php",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "author@example.com",
  "license": "ISC"
}

Now we can install some node dependencies:

$ npm install --save react express babel body-parser

Creating the rendering service.

We can now use express to create a super simple http server which only handles the rendering of views: server.js:

'use strict';

require("babel/register");
var React = require('react');
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');

var app = express();
app.use(bodyParser.json());
app.use('/', function(req, res) {
    try {
        var view = path.resolve('./views/' + req.query.module);
        var component = require(view);
        var props = req.body || null;
        res.status(200).send(
            React.renderToString(
                React.createElement(component, props)
            )
        );
    } catch (err) {
        res.status(500).send(err.message);
    }
});

app.listen(3000);

What this little nugget does, is accept http requests. It'll use the body-parser middleware to convert the JSON body into data we can feed to our React view. It'll also allow us to specify a module name in the query parameters, which we'll use to detect which view we want to render. The try/catch block ensures we return a 500 response when the view isn't found or an error occurred.

All that's left now for the node side of this setup is the actual view. We'll create our hello components in the views/hello.js file:

import React from 'react';

class HelloComponent extends React.Component {
    render () {
        return <h1>Hello, {this.props.name}!</h1>;
    }
}

HelloComponent.defaultProps = { name: 'World' };

export default HelloComponent;

Note that we're using using ES2015 (a.k.a. ES6) syntax here. Since we're using babel we get to use all the fancy new features the language has received (classes, arrow functions, etc.)

We can now start the node application and see what kind of output we're getting.

node server.js

This will be a foreground process for now. When we open the browser and point it to http://localhost:3000 we'll see "Hello, World!" being rendered on the screen, perfect! We've got our view rendering service ready for action.

Using the node service in PHP

Since we're creating the most simple application out there, we'll only focus on the routes file located at app/Http/routes.php. After removing the default route, we'll create a new route:

use GuzzleHttp\Client;

$app->get('/{name:.*?}', function($name) use ($app) {
    $client = new Client(['base_url' => 'http://localhost:3000']);
    $response = $client->post('/', [
        'json' => ['name' => ucfirst($name ?: 'World')],
        'query' => ['module' => 'hello'],
    ]);
    $contents = $response->getBody()->getContents();

    return response($contents, 200);
});

This route accepts GET requests on / with an optional name segment. It'll then post a JSON body with our name parameter to our node application (which is listening on post 3000). The response is then returned back to the client.

Let's start our PHP server and see if everything works! Note that we still need to have our node application running, so we'll have multiple terminal windows open.

In our first terminal window, the node application:

node server.js

In our second application, the php application:

php artisan serve

If we now open up our browser and Hit That Route™ (http://localhost:8000), we should see Hello, World! rendered on the page. If we then add our name to the URL (http://localhost:8000/frank) we should see Hello, Frank!

Et voila! We've just created one of the world's most complicated "Hello World" example applications ever, hooray!

Back to reality.

When dealing with complexer interfaces/ui's the offloading of template rendering can become very worthwhile. Views rendered in the client side and the server side can share the same codebase. There's less code to maintain and the setup overhead is very minimal. When experimenting with this setup, I've had a throughput of >4000 requests handles per second inside a vagrant (ubuntu) VM, I was pretty impressed by this.

Concluding

Rendering the templates of your PHP application using React is a very feasible option. It's not difficult to setup and it's easy to maintain. Already have node in your stack? Perhaps pre-rendering some components, see what it does for your application.

Have fun!

Subscribe for updates

Get the latest posts delivered right to your inbox.

Sign up