Wait. Isomorphic Webapps? What the heck is that?
Traditionally webapps generate HTML on the server and send it over to the client. This has changed with the recent rise of client-side MVC frameworks such as Angular.js, Backbone.js or Ember. But generating HTML views on the client has both pros and cons. Isomorphic webapps try to close this gap by enabling you to use the same technologies for generating views both on the server and on the client.
The rest of this article explains how to build isomorphic webapps with React on the JVM by utilizing Nashorn and Spring Boot to pre-render React views on the server. The example code is hosted on GitHub and focuses around the official React.js tutorial - a comment box example with markdown support. If you're not yet familiar with React.js, just follow the steps described here. You may also want to read my Nashorn Tutorial later, but it's not mandatory for this blog post.
React Views and Templates
The main React component as described in the official tutorial looks like this:
In order to render this component in the browser we define a function
renderClient which simply calls
React.render to attach the view to the content container of the page:
Calling this function renders the comment box component into a pre-defined content
div container by passing an array of comment objects as data. We cannot call this function on the server because it highly depends on the browser DOM being present (see
The great part about
React.render is that it respects pre-rendered HTML from the server:
If you call
React.render()on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience.
So, the next step is to render the whole view on the server. We need two things to achieve that: (i) the pre-rendered html and (ii) the pre-processed JSON data as input for
Let's define a template
index.jsp with model attributes
data. Content will be filled with the pre-rendered HTML of the comment box while data will be replaced with the JSON array of comments so React can initialize all the components props and states on page-load.
Server-side rendering with Nashorn
The above code evaluates all scripts needed for the comment box tutorial. The helper method
read simply reads a file from the classpath by creating a new io reader:
React doesn't evaluate properly on Nashorn without some fixes. I've created a file called
nashorn-polyfill.js to address those issues (see this issue).
This is the content of this file:
The following Java method demonstrates how to render the HTML of the comment box tutorial via Nashorn on the server:
renderServer located in
commentBox.js. It looks quite similar to
renderClient as described above:
renderServer accepts a native Java list of comments as argument. Since the React components implemented in
Java.from. The function
React.renderToString finally creates the desired view and returns an HTML string.
The Main Controller
Finally we wrap all things up into a Spring Boot controller. Remember that we need both model attributes
data to properly render
index.jsp. We just learned how to generate the content HTML with Nashorn. But we also need the initial JSON data so React knows about the components props and states. This is quite simple by utilizing Jacksons
ObjectMapper to convert the list of comments into the appropriate JSON data.
That's all. The main controller renders the HTML for all available comments on the server.
React.render gets called in the browser on page-load, preserves all server-rendered markup, initializes the internal props and states of the components and registers all event handlers. The comment box gets auto-refreshed every couple of seconds and newly created comments will directly be attached to the DOM without waiting for the server to answer.
Isomorphic server-rendering in this example has many benefits compared to generating the views solely on the client: The page is fully search-engine optimized (SEO), so search engines such as Google can parse every comment properly. There's also no UI flickering on first page-load: Without server-rendering the browser first shows an empty page, then fetches all comments and renders the markup. Depending on the performance of the clients hardware you'll notice some flickering on startup. This is even more of an issue on mobile devices.
I hope you enjoyed reading this post. If you want to learn more about the Nashorn Engine you probably want to read my Nashorn Tutorial. The full source code of this example project is hosted on GitHub. Feel free to fork the repository or send me your feedback via Twitter.