Dave King

Rendering React Components Using Perl

React + 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.

When adopting React, we needed to keep rendering our pages on the server. First, server-side rendering helps provide a fast initial page experience – the page is visible, and then the JavaScript attaches to it, making it functional. In constract, client-side only solutions will show a blank screen after which the page then suddenly appears – this can be a jarring experience for users. Secondly, to ensure that our campaigns are discoverable through search engines it’s important that we follow best practices for SEO. At the time, frameworks like Angular and Ember didn’t yet easily support server-side rendering, so they could only be used on the client.

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 Layout or 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 props object.

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.)

Campaign Page

Now here’s the same page with the various parts of it in React broken out.

Annotated Campaign Page

To render the page, we need to render four different React components – AdminDetails, CampaignTabs, ShareOptions, and 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.

React + Perl

Template Toolkit

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 AdminDetails and 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.

The react-component block also adds the name and props into the DOM as data attributes. These attributes will be picked out by our client-side JavaScript to re-render the component on the client.

Server-Side Rendering With Webpack

We use webpack to build bundle files from our JavaScript source. Webpack is traditionally used to generate bundles for a browser environment, but it can also be used to generate bundles that work in a Node.js context. Preprocessing your JavaScript files using webpack also allows you to use tools like babel-loader, which allows you to use ECMAScript Next syntax today, even if your client-side or server-side execution environments doesn’t yet have native support for them.

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 window. (This map must be placed into a global scope so that it can be accessed by JavaScript that is not built as part of the webpack bundle.)

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 React.renderToString with a named component for any JavaScript context that has evaluated the bundle file.

Finally, we evaluate our webpack bundle in a V8 context using the JavaScript::V8 Perl module. This context has access to the 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.

Client-Side Rendering

Okay, now our server is generating markup using React. In order to create interactive components, however, we need to mount the React components on the client-side – otherwise we won’t have any JavaScript event handlers. This requires finding each DOM node that was generated using the Reify plugin, and then calling 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 true or 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.

Web Service or JavaScript Execution Context?

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.

However, this comes with its own drawbacks. A JavaScript context can’t easily do its own I/O – it works best as a dumb rendering engine. This means we still need to do all our web service calls in Perl. Additionally, as we’re using a process-based approach to serving the site with Perl’s Starman web server, each of our processes must spin up its own V8 context, causing an increase in the overall memory overhead – if you have 16 workers on a box, that’s 16 V8 contexts running at a given time. Additionally, periodically a worker process will die, causing a new process to be spun up, which must create a new V8 context that evaluates our JavaScript bundle. This causes a slight delay (300-500ms) before that process is ready to serve traffic.

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.

Impact of V8 Switch

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 dispatcher.getStore('UserStore').

Our production 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

Currently we render between 4-8 React components per page. Adding more components creates more overhead because the V8 context must be invoked for each one, which requires binding Perl data structures into JavaScript. Ideally the entire page is in React, allowing us to only perform this translation layer once, but the more react-component template calls we have on a page, the more this translation starts to dominate the rendering time of the overall page. This is even more important given how we initialize our stores – the storeData initializer is a fairly large Perl dictionary and converting this to JavaScript over and over again can be fairly costly.

As an example, we have a UserImage component for displaying a Tilt user’s image. There are many places on the site where we display multiple images at once such as the list of contributors to a campaign. It would be a really bad idea for us to only convert the image calls into React, because then if a campaign calls 100 user images, we’re invoking all of the the wrapper functions (and conversion between Perl and JavaScript data structures) 100 different times. It would be better to create a parent component in React, take a big dictionary with all the data that it needs to render its children, and then have it create the 100 different child elements. This way, the conversion from Perl dictionary into JavaScript object is done only once, speeding up the overall render time for the page.

Other Resources

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:

Thanks to Forest Belton for building the initial Tilt implementation of React and reviewing a draft of this article.

« 7 PostgreSQL data migration hacks you should be using (but aren't) Join us at the Inaugural Snowplow Meetup @TiltHQ »