Rendering React Components Using Perl
The Tilt.com website was originally built using Perl. Over the years, we added jQuery UI to build widgets that had dynamic page behavior. Perl on the backend and jQuery UI on the frontend helped us get to where we are today, but about a year ago it was clear to us that we needed to start introducing a more modern technology stack. We chose React for our view layer and Node.js for the application layer. We recently launched a revamp of our settings page which is built on top of this new technology stack.
We’re always busy working to improve the Tilt website, so stopping everything to port the rest of the site to a new stack would take time that we can’t afford. Instead, we needed to gradually introduce the new technology – ideally side-by-side with our existing codebase. In this post I’ll go through how the Tilt website does server-side rendering of React components from application servers running Perl Dancer. This has allowed us to modernize our tech stack without requiring us to rewrite our entire codebase.
Why adopt a new technology stack? The mismatch between server-side and client-side rendering logic makes it difficult to build pages with more dynamic behavior. Additionally, after a few years of adding features, we had a fair amount of technical debt built up around our existing templates – like any typical jQuery codebase. We had gotten to the point where code didn’t have a great separation of concerns and it had become really hard to figure out how a page was updated after a user-initiated action. React components provided a way to isolate rendering logic into one place that would be more easily testable.
Server-Side Rendering With React
On the server, React.renderToString generates markup from a React component. On the client, React.render initiates the React component lifecycle on the generated DOM elements – this can do things like attach event listeners to the DOM or fetch data from the server using an XMLHTTPRequest. Each of these render functions comes with a
props argument – props are the configuration of the component.
If your entire page is in React, setting up the client to attach to generated markup from the server is pretty straightforward – you render a
Page component, your
props are some application globals, your
node is the document body, and it’s fairly straightforward to hook the two together.
However, when only part of your page is in React, you may need to make several
renderToString calls on the server. On the client, this becomes several
render to different DOM nodes, each with a different component. Each of these components may need a different
Here’s the Tilt campaign page. (For this particular campaign, our Director of Product had his bike stolen and we pooled money to help him buy a new one.)
Now here’s the same page with the various parts of it in React broken out.
To render the page, we need to render four different React components –
Masthead. Each of these may need to be rendered with different
props arguments on both the server and client – and any arguments passed to the server must also be available to the client.
Here’s a big-picture view of the architecture that we use for server-side rendering. Next, I’ll walk through how each of these pieces works in detail.
Our Perl application server uses Template Toolkit to generate HTML that serves our campaign page. Template Toolkit is one of the earliest templating libraries and it provides a way to build plugins that execute arbitrary code to generate HTML.
Here’s what a part of the template to render the campaign page looks like. Template toolkit’s INCLUDE directive allows you to include a block defined in another file – here we call out to the
react-component block. I’ll show the implementation of that next.
This calls the Template Toolkit block ‘react-component’ in two places – the
ShareOptions. Both of them are templated specifically with options that will become the
props of the newly rendered React component. For example the
ShareOptions component takes a specific
utm code that indicates what position on the page the content was shared from.
Here’s the definition of the
react-component block. It calls a Template Toolkit plugin called “Reify” which will return the React markup for the particular component component. I’ll go into the implementation of the Reify plugin in the next section.
react-component block also adds the
Server-Side Rendering With Webpack
In order to be able to support the Reify
render call in our
react-component block, we need a way to convert a name (a string, “AdminDetails”) into a component (the React component definition that is created with
React.createClass). We do this by including a
ReactTemplates map in a global scope – on the server, the node.js
global object, on the client, the
In our production code we generate this map using a complicated webpack
transform – however, the following snippet is an example of how you can get started:
This map allows us to create a global function
renderToString that transforms a component name and component props into a string. This function being available as a global allows us to call into
renderToString function that’s available on the
global variable. Here’s the implementation of the Template Toolkit Reify plugin.
This isn’t the only way you can do server-side rendering without a Node.js app server. We’ve experimented with SpiderMonkey and an Express web service that serves React templates in response to JSON-over-HTTP. I’ll go into why we recently switched to using a V8 execution context later in this post.
React.render with the same arguments (component and props) that were called with
React.renderToString on the server.
Here we use the HTML5 data properties that were set on the generated element. In the DOM, this looks like:
(The JSON must be escaped to avoid XSS injections.)
Our bootstrapping code then walks the DOM and calls
React.render with the un-escaped JSON:
We now have a React component generated by our Perl app server that’s been properly mounted in the DOM, allowing us to develop code on a new tech stack without throwing away all our old code!
Server-Side Rendering Challenges
So far I’ve described the system that we’ve been using in production to render parts of Tilt.com for the last nine months. Below are some of the wrinkles that we’ve run into using this approach.
Getting Perl Data into React Components
A good deal of our application server is still in Perl – it is common for our application to make web service calls which then end up being rendered into our templates, some of which happen to be in React. Unfortunately, Perl does not have built-in
false values. This requires us to pass in 0 and 1 as props into React components that would more correctly take boolean arguments.
This is really just an inconvenience – it causes warnings in development mode when using prop validation. Otherwise, it’s a harmless annoyance.
We recently switched our server-side React rendering approach from using a web service (JSON over HTTP) to using an V8 execution context. The main reason that we did this was to improve the availability of our site after deploying new code. During a code deploy, there is a small window where you have to restart services to start serving new code. If the app server is responsible for client-side code (through a script tag that loads from the CDN) and the rendering service is responsible for the server-side code, you have to keep these services in sync to ensure the availability of the site. Using a V8 context, a restart of an app server after a deploy immediately starts serving new code on both the client and the server.
So far these drawbacks haven’t been a problem for us but we’re keeping a close eye on our overall system health. We monitor memory using Diamond and instrument our applications to send data to StatsD. We then visualize it using Grafana (Stay tuned for a more in-depth post on this!). Here’s what the deploy to one of our servers to use embedded rendering ended up looking like – the server uses about an extra GB of memory after the deploy.
Using Flux on the Server-Side
Evaluating our webpack bundle takes ~400ms. Rendering an individual component takes ~20ms. Therefore, the key to performance is to use the same V8 context to serve requests from multiple customers. However, Flux-based setups often will use singleton global stores. This means that carelessness with how you’ve set up your data instantiation could lead to customer data leaking between different render requests to the same process.
We avoid this problem by using the dispatchr library, which requires that stores be access through an instance of a dispatcher class that is unique for between requests by different customers – for example, the User Store is no longer a global variable, but is retrieved through
renderToString function also takes a
storeData argument that contains all of the data for our Flux stores. Rendering a component to a string first initializes each of the stores using a
rehydrate method, and then passes the dispatcher object into to the rendered component’s subtree through React’s context feature.
This setup also lets our components remove the antipattern of props as initial state – the Flux stores are always the source of truth for most data on both the client and the server.
Minimizing Render-Time Overhead
As an example, we have a
We aren’t the only team running a hybrid tech stack. If you’re just getting started with server-side rendering, there are a lot of other resources to learn from. Here’s a sampling of articles that might be useful in learning more about server-side rendering in general, and how to do it in React specifically:
- A PHP plugin for rendering React from PHP: https://github.com/reactjs/react-php-v8js
- Airbnb’s Rendr library for server-side rendering of Backbone views: https://github.com/rendrjs/rendr
- A walkthrough of how to achieve server-side rendering with React using Handlebars – http://www.crmarsh.com/react-ssr/
Thanks to Forest Belton for building the initial Tilt implementation of React and reviewing a draft of this article.