Isomorphic React Apps in Symfony with the help of Webpack
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.
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 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.
- 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.
I have prepared a sandbox to see what we are talking about. It is live at http://symfony-react.limenius.com/.
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.
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.
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.
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
Whereas in production we will serve the packages from the generated files. Generate them with:
webpack --config webpack.config.js -p
-p option stands for optimize and minimize.
config.yml we just tell the framework to load these assets from files:
Now we can load our assets as:
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
<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
In this case, we are loading SCSS files.
In our template we can now write:
Server and Client-side rendering
What we want is to render React components server-side with Twig tags like:
In order to do that, we are going to use the React on Rails node package.
We also need to build the server-side Webpack bundle with:
webpack --config webpack.config.serverside.js --watch
--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':truein 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!
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.
There are further features that we would like to have, like:
- Provide a second example with Redux/Flux (although the example code Js generated by React on Rails will work out of the box).