Routing-aware React server-side rendering on PHP with Silex and react-router

Not everybody can use Node.js on the server side to be able to craft truly universal JavaScript applications. Still, one might want to set up server-side rendering to enhance React's performances, and of course allow JS-uncapable clients to use their app seamlessly (at least regarding navigation).

In , I quickly demonstrated how to render React code with PHP's v8js extension. We will now see how to handle the routing with Silex and react-router.

Note: this article's snippets are written using ES6.

First, let's define our routes:

import Root from './components/root';
import HomePage from './components/homePage';
import OtherPage from './components/otherPage';

export default {
path: '/',
component: Root,
indexRoute: {
component: HomePage
childRoutes: [
path: 'other',
component: OtherPage

Here are the components:

import React from 'react';

export default class Root extends React.Component
return (

import React from 'react';

export default class HomePage extends React.Component
return (
<div>Hello world!</div>

import React from 'react';

export default class OtherPage extends React.Component
return (
<div>Hi there! I am another page.</div>

We add the entry point (typically, app.js) to the mix:

import React from 'react';
import ReactDOM from 'react-dom';
import { renderToString } from 'react-dom/server';
import { match, RoutingContext } from 'react-router';
import createBrowserHistory from 'history/lib/createBrowserHistory';
import routes from './routes';

(function() {
'use strict';

if ('undefined' !== typeof document) {
// We're on the client, nothing new here
history={createBrowserHistory({ queryKey: false })}
document.getElementById('app') // don't use document.body
} else {
// We're on the server, this will be executed by v8js
location: uri
function(error, redirectLocation, renderProps) {
? error.message
: renderToString(<RoutingContext {...renderProps} />)

You can see I used the same hack as in my previous article to tell apart client and server-side rendering routines. You can also see that on the server, the matched location used for routing comes from a variable named uri, that wasn't declared anywhere. To understand this, we must jump to index.php:


use Silex\Application;
use Silex\Provider\TwigServiceProvider;


$app = new Application();
$app['v8js'] = new V8Js();

$app->register(new TwigServiceProvider(), ['twig.path' => __DIR__.'/views']);

function ($uri) use ($app) {
$js = implode(';', [
'var uri = \'/'.$uri.'\'', // inject URI

$content = ob_get_clean();

return $app['twig']->render('app.twig', compact('content'));
->assert('uri', '.*')
->value('uri', '');


We end with our Twig template:

<!DOCTYPE html>

<meta charset="utf-8" />

<div id="app">{{ content|raw }}</div>
<!-- This script tag is the reason we needed
a container inside the body -->
<script src="dist/app.js"></script>


In order for this to work, your browser must support the History API, and you must use a web server such as Apache configured in the likes of this:

<IfModule mod_rewrite.c>
Options -MultiViews

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]

You can then try out server-side rendering by commenting out the script tag in your template, in order to notice your React code truly is rendered by PHP! Navigate to /other and you will see the alternative content show up.

Your app can now be browsed with and without JavaScript!