skip to content

Isomorphic React Apps in Symfony with the help of Webpack

Let's have SSR React in PHP applications. I wrote some libs for it.

It is a strong tendency in our sector to have increasingly more frontend code and present the user with rich client applications. As a result, new and better ways of organising client code have appeared.

We, in Limenius, as Symfony lovers, have been exploring ways to combine it with these techniques. In the first versions of Symfony 2 Assetic was the preferred way to manage assets. However, as the frontend world develops better tools, it makes sense to use them. At the present moment, we consider Webpack a great choice and we prefer it over Grunt or Gulp for new projects. On the other hand, after long time working with Backbone.js and also Angular 1, we are very happy with React.js.

One of the (many) differences between React and Angular 1 is the ability to render components to strings, allowing us to render them server-side. This should mean a better SEO, ability to cache React-redered components with Varnish, faster load times and also present content to users that have JavaScript disabled.

This means that we have the possibility of rendering React components beyond SPAs, in fields like classified ads, that rely on SEO and also want fast content load, while providing an up-to date user experience.

In order to have this, what would we want?

  • To have a nice frontend set up with Webpack, with well organised assets, hot-reloading that refreshes the page on changes, that is comfortable and agile in development and produces optimized assets in production.
  • To be able to render React components from Twig tags and choose if we want to render them server-side, client-side, or both.
  • To have debug tools, like access to console.log messages, when rendering JavaScript server-side.
  • To do this without the need of running a node.js HTTP server that renders React for us and that would add operational complexity.

Can we have it? Yes we can. While exploring what would be the best way to do this I stumbled upon the React on Rails project, that does a great job. Can we have the same in Symfony? Of course we can! (don’t worry, we won’t write any Ruby ;). For that we have published the following on Github:

  • ReactBundle. A bundle to render React components in Twig templates and be able to configure if we want them to render client-side, server-side or both.
  • PhpExecJs. A library to execute JavaScript code from PHP, inspired by ExecJs. At the moment it will call node.js in a process node using Symfony Process. It is in the roadmap to support other ways to run JavaScript from PHP, like the V8js extension. Update: V8Js support has been added.
  • A sandbox that integrates the bundle, provides an example App and has a default Webpack configuration.

We will also use the React on Rails npm package to expose and render our React components with error logs and whatnot.

Live example

I have prepared a sandbox to see what we are talking about. It is live at http://symfony-react.limenius.com/.

The code is on GitHub.

If you examine the HTML source code in the page, you will see that the components are rendered by the server. On the other hand, if you follow links or use the search input, it will be the client-side React that will change the DOM dynamically, so we have the best of the two worlds.

Why Webpack? Webpack Set Up

Webpack allows us to manage frontend dependencies with CommonJs or AMD require module loaders. This is a saner way of managing complex dependencies than placing all the <script> tags in the HTML and hope that they are in the right order.

It has also tools for optimizing assets in production, whereas in development we can have sourcemps and a hot-reload server that watches for changes and refreshes the page we are working on automatically as soon as the assets bundle is rebuilt.

It also has loaders for every kind of asset you may think of, from SCSS to JSX, including the possibility of writing the latest JavaScript and make Webpack traduce (transpile, if the word doesn’t give you a rash) to JavaScript readable by today’s browsers.

Two Webpack config files

We will have two Webpack config files. One will be used for code meant to run client-side, and will contain our React code, as well as our style asssets, fonts and other kind of assets that we may need.

The other, webpack.config.serverside.js will produce a bundle that will be used as context when we are rendering React components server-side. More on this later, when we talk about server side.

Hot reloading

While developing, we can run the Webpack dev server, that will serve our assets in localhost:8080 (configurable, of course). Run it with:

webpack-dev-server --progress --colors --config webpack.config.js

And then in our config_dev.yml, under the framework section, we can define a webpack section:

framework:
    assets:
        packages:
            webpack:
                base_urls:
                    - "%assets_base_url%"

In parameters.yml:

parameters:
    assets_base_url: 'http://localhost:8080'

Whereas in production we will serve the packages from the generated files. Generate them with:

webpack --config webpack.config.js -p

Where the -p option stands for optimize and minimize.

And in config.yml we just tell the framework to load these assets from files:

framework:
    assets:
        packages:
            webpack: ~

Now we can load our assets as:

<script src="{{"{{"}} asset('webpack-dev-server.js', 'webpack') }}"></script>
<script src="{{"{{"}} asset('assets/build/client-bundle.js', 'webpack') }}"></script>

Note that you should disable the loading of webpack-dev-server.js, that handles the communication with the Webpack server, in production with a conditional tag if app.environment == "dev".

Separate CSS and JS

For the best loading time perception, CSS is best served in the <head> tag, while JavaScript is best served at the end of the <body> tag. We can tell Webpack to extract the CSS into a separate file so we can insert it in a separate place. This is done by the extract-text-webpack-plugin, that we configure in the webpack.config.js:

var ExtractTextPlugin = require("extract-text-webpack-plugin");
var extractSCSS = new ExtractTextPlugin('stylesheets/[name].css');

//..

    plugins: [
        extractSCSS,

In this case, we are loading SCSS files.

In our template we can now write:

<link href="{{ "{{" }}asset('assets/build/stylesheets/main.css', 'webpack')}}" rel="stylesheet">

Server and Client-side rendering

What we want is to render React components server-side with Twig tags like:

{{ "{{" }} react_component('RecipesApp', {'props': props}) }}

In order to do that, we are going to use the React on Rails node package.

After installing ReactBundle, that defines this Twig function, the only change that we have to do to our JavaScript code is to register the components that we want to render in this library:

import ReactOnRails from 'react-on-rails';
import RecipesApp from './RecipesAppServer';

ReactOnRails.register({ RecipesApp });

We also need to build the server-side Webpack bundle with:

webpack --config webpack.config.serverside.js --watch

(without --watch on production).

And then they will become available. If we are rendering the code both server and client side, what this will do is:

  • Call node.js from PHP (or call the V8Js PHP extension, if available) to get a string representation of the component, and insert it in the generated HTML.
  • When the browser loads the page, the client-side React will compute the component and, at the time of inserting it in the HTML, it will discover that it is already rendered, and it won’t render it again. Instead, it will take control over the component to re-render it dynamically in case its state changes.
  • If the server side code produces console.log messages, it will re-display them in the browser’s console (enable this with 'trace':true in Twig or see the configuration options of ReactBundle to enable this option project-wide, for instance in the dev environment).

Of course we can disable the server-side rendering for a component or project-wide and still be able to insert it the component from Twig templates. We can also disable the client-side rendering, but that is less common, although possible.

And that is it. Profit!

Different entry points for Client-side and Server-side JavaScript

Although we will share almost all our React code between Server-side and Client-side, there are some things to care about. Normally, we will want to wrap the application in a wrapper for server-side that sets up the react-router in a different way, and also prevents the components from making Ajax requests. This is a common issue in every complex enough isomorphoc application. The good news is that as this issue is not particular to Symfony we have plenty of examples from people running isomorphic React in other platforms.

The end

There is much to talk about this topic. If you are interested, check the documentation of the ReactBundle, run the Sandbox or check its documentation and code.

Further work

There are further features that we would like to have, like:

  • Make PhpExecJs be able to run other JavaScript runtimes apart from node.js (V8Js, Spidermonkey?) update:: V8Js is available.
  • Provide a second example with Redux/Flux (although the example code Js generated by React on Rails will work out of the box).