SSR React with Symfonyfor the Strong of Spirit Workshop

React & React Bundle

Theory
About the React Bundle.

Set up

We have prepared a new Symfony Project with some setup already done for you. Let's check it out:
git clone https://github.com/Limenius/workshop-symfony-react.git
git checkout 04-start-react
And install composer and npm dependencies
composer install
npm install
If we start our PHP server:
php -S 127.0.0.1:8080 -t public
npx encore dev --watch
We should see that we have the following URLS available:
Which will be HTML pages that hold a list of James Bond movies and detailed views for every movie.
And these ones:
Which are a JSON api that will allow us to GET the same resources (list of movies and individual Bond movies).

React & React on Rails

Let's create a simple client side React App. Let's start by installing the packages we are going to use:
npm i --save react react-dom react-on-rails
npm i --save-dev babel-preset-react
And then the React Bundle, that will provide us a Twig extension that we can use to render components in client and server side.
composer require limenius/react-bundle
Let's configure the bundle initially for client side rendering only first. Create config/packages/limenius_react.yaml with this content:
1
2
limenius_react:
    default_rendering: "client_side"
And let's create our first React component, in assets/js/MovieComponent.js
1
2
3
4
5
6
7
8
9
10
11
12
import React from "react";

const MovieComponent = ({ movie }) => (
  <div className="movie">
    <div className="movie-image">
      <img width="250" src={`/images/${movie.image}`} />
    </div>
    <div className="movie-name">{movie.name}</div>
  </div>
);

export default MovieComponent;
And a listing of movies that uses this component in assets/js/MovieList.js
1
2
3
4
5
6
7
8
9
10
import React from "react";
import MovieComponent from "./MovieComponent";

export default class Movies extends React.Component {
  render() {
    return this.props.movies.map(movie => (
      <MovieComponent key={movie.slug} movie={movie} />
    ));
  }
}
To use a root component, we need to register it using React on Rails. Create this entry point in assets/app.js
1
2
3
4
5
import ReactOnRails from "react-on-rails";
import MovieList from "./MovieList";
require("../css/app.css");
 
ReactOnRails.register({ MovieList });
Now we can use it from Twig, rewrite the template in templates/movies/list.html.twig:
1
2
3
4
5
{% extends 'base.html.twig' %}

{% block body %}
{{ react_component('MovieList', {'props': props}) }}
{% endblock %}
There is only one thing left. Enable support for React (a babel preset) in encore. Edit webpack.config.js so it reads:
1
2
3
4
5
6
7
8
9
10
11
12
var Encore = require("@symfony/webpack-encore");

Encore.setOutputPath("public/build/")
  .setPublicPath("/build")
  .addEntry("app", "./assets/js/app.js")
  .cleanupOutputBeforeBuild()
  .enableBuildNotifications()
  .enableSourceMaps(!Encore.isProduction())
  .enableVersioning(Encore.isProduction())
  .enableReactPreset();

module.exports = Encore.getWebpackConfig();
(We have added .enableReactPreset() at the end of the configuration.)
With this, if we restart webpack encore it should work. Stop it and run this:
npx encore dev --watch
And we point our browser to localhost:8080/. We should see a list of Bond movies
Exercise
Can you write a component for the movie detail page and make it render?
You will need a React component in assets/js/MovieDetail.js
Render it from Twig in templates/movies/detail.html.twig
...and enable it in:
assets/js/app.js
Reveal the solution
Got lost?
The code up to this point is in the tag 04-client-side of the repository https://github.com/Limenius/workshop-symfony-react.git
This means:
git reset HEAD --hard
(To discard your changes)
And then:
git checkout 04-client-side

Enable Server Side Rendering

What do we need to enable SSR?
Only few things, really.
First, we need to create a new webpack config in webpack.config.ssr.js
1
2
3
4
5
6
7
8
var Encore = require("@symfony/webpack-encore");

Encore.setOutputPath("var/webpack/")
  .setPublicPath("/")
  .addEntry("app", "./assets/js/app.js")
  .enableReactPreset();

module.exports = Encore.getWebpackConfig();
And now we need to configure React Bundle to handle it. Edit config/packages/limenius_react.yaml:
1
2
3
4
5
6
7
8
limenius_react:
    default_rendering: "both"

    serverside_rendering:
        trace: true
        fail-loud: true
        mode: "phpexecjs"
        server_bundle_path: "%kernel.project_dir%/var/webpack/app.js"
And now open a new terminal and run:
npx encore dev --watch --config webpack.config.ssr.js
Open one of our pages. It should look the same (after all, that is the point).
Exercise
Open the source code of the page and see if you can find the HTML code of our server side component.
Now change the configuration of config/packages/limenius_react.yaml of the option default_rendering from both to client_side and afterwards to server_side
Can you spot the differences?
Got lost?
The code up to this point is in the tag 04-ssr of the repository https://github.com/Limenius/workshop-symfony-react.git
This means:
git reset HEAD --hard
(To discard your changes)
And then:
git checkout 04-ssr