Server side rendering of React apps in Spring Boot is supported using Spring’s script template views. Although still young, script template views provide the foundation for building isomorphic React apps. I will show you how this is done using React’s server side rendering capabilities and later I will explain how to add support for React Router and Redux.
In a previous post I wrote a little over a year ago, I covered the very basics of Spring’s script template views with a small app that used EJS script templates. If you followed a link from somewhere expecting that post, you’ve been redirected here because this post will be much more complete.
You can find the full source of the sample application I built on my GitHub repo. If you’d like to see an alternative to JSR-223, be sure to check out the J2V8 branch of the project. The J2V8 version performs better than the Nashorn version.
The basic setup is still the same. First I’ve defined a
ViewResolver typed bean which returns a new
ScriptTemplateViewResolver with prefix of
/public/ and suffix of
.html. Next I’ve defined a
ScriptTemplateConfigurer bean. The recommended approach is to instruct Spring to create a non-shared
ScriptEngine when using libraries like React that are not designed for concurrency.
Lastly, I’ve defined a couple of scripts that are on the classpath named
server.js, and told Spring that the render method is called simply
Handing requests to render the script templates is done by creating a root or index controller. I can’t simply setup a root
@GetMapping("/") because that will intercept the client side assets. When requests come in for
client.css for example, the response body would be the contents of a script template. To circumvent this, the get mapping uses a regex negative lookahead to ensure it does not match against any static assets. It’s certainly not a very friendly solution, but it works for this example.
Server Side Scripts
First is the
server.js script which will be built using Webpack and written to
src/main/resources/public. Using a bundling utility like Webpack makes it easier to use libraries like React in server side rendering, because you don’t have to tell Nashorn about them when initializing a
renderToString from ReactDOM, I can render the
App component server side. I’ve attached the
render function to
window which will make it visible to the script engine when it needs to render the templates. The result will be injected into the template where the
SERVER_RENDERED_HTML placeholder exists and the final result of the render function will be written to the view.
Next is the
polyfill.js script which I’ll place in
src/main/resources/static so that it doesn’t get clobbered by the Webpack build. This script doesn’t need to ever get rebuilt, it’s simply there to provide alternatives to variables that are not available on the server.
The next piece of the puzzle is a template called
index.html. This sample shows the result of a Webpack build. The template has been written to
src/main/resources/public and the static assets have been injected. What’s important to take note of is that these assets are being resolved to an absolute path. No matter what page gets visited, they will always be resolved from the root path and not relative. Also worth mentioning is the
SERVER_RENDERED_HTML placeholder that I spoke about earlier. This is where the result of the server side render function will be injected.
Rendering Client Side
client.js script is very similar to the
server.js script. The main difference is that it uses the browser side
render function from ReactDOM and renders the
App inside an actual HTML element.
Let’s add Routing
This is where the benefits of server side rendering become more obvious. I’m using React Router 4, which is still in alpha stage, but works for this example. I tried really hard to make this work with React Router 3, but I kept hitting one road block after the next. React Router 4’s major implementation change is that it’s now more focused on being component based and embracing the React way of doing things. You can read more about why here.
Let’s start by installing React Router 4.
Populate the Model
In the index request mapping, I need to add the model information which will be made available to the server side render function when the view is resolved. The only thing I’m adding is a Java
Map which is serialized to a JSON encoded string using Jackson’s
Contained inside the map is a single key named
location which maps to the servlet path from the request, as well as any query string parameters. This value will be used by the server render function to initialize the
Revisiting the Server Side Render Function
server.js script requires some additional instrumentation to support React Router. The main difference is that the render function now accepts a second argument containing a model that I just populated in the index request mapping. I will retrieve the
location property and use it to initialize the
ServerRouter component. The result will be injected into the template at the same placeholder as before. The location will get passed down the component tree and any child routing components looking for that specific location will render.
Time to Update the Client
client.js script needs to be changed as well. It’s still quite similar to the
server.js script, but uses a
BrowserRouter instead of a
ServerRouter. I don’t need to initialize any location this time because it will listen to the browser URL to determine what to render when the client side takes over.
Preloading State with Redux
Now that the app is capable of rendering server side and can handle routing as well, it’s time to add preloaded state to the mix using Redux on the server. To get things started, I’ve added Spring Data JPA and Spring Data REST to the project. I’ve also added an H2 in-memory database and setup an
Item entity with a REST repository providing HAL formatted output. Be sure to refer to my GitHub repository as I’ll only be showing key elements here.
I’ve setup the REST repository with a
/api. In order to prevent the index request mapping from intercepting those calls, I need to update the
@GetMapping. You’ll also see that I’m adding a new model attribute named
initialState which contains some data from the item repository.
Rendering the Preloaded State on the Server
In order to preload the state in Redux and maintain server side rendering, I will create a store and pass it the initial state from the model. The markup is only slightly different, there’s now a top level
Provider component which receives the store. Lastly, I’m injecting the initial state into the template using the
When I first published this post, I was using
JSON.stringify(initialState) to replace
SERVER_RENDERED_STATE. After reading an interesting post on how this introduces an XSS vulnerability, I’ve updated it to use
Adding Preloaded State to the Template
The only thing I need to do in the template is define a script at the end of the body which defines the
__PRELOADED_STATE__ variable who’s value will be injected using the
Rendering the Preloaded State on the Client
Yet again, the client is similar to the server. It also wraps the existing markup with a
Provider component that receives an Redux store. The main difference is where the preloaded state comes from. This time it comes from the
__PRELOADED_STATE__ variable that I just defined in the template.
That’s it! This was quite a learning experience for me. I tried several things along the way that failed, but never lost sight of my goal. The index request mapping saw several iterations, because I’m not a regex expert, but who is? Perhaps Spring even offers a better approach that I’m ignorant of.
I also spent a lot of time trying to make this work with React Router 3, which ultimately proved to be unfruitful. Adding Redux was pretty straight forward with a little guidance from their documentation. As always, I welcome any feedback. If you think this post needs some improvements, feel free to comment.