Architecting Better Landing Pages

Building robust static sites in React

Despite the landing page being the first point of contact, it’s often the last consideration when creating a product. The holy grail of landing pages is instantly rendered UI backed by rich interactive components on the client. This means that it will be blazing fast on initial load, exhibit the seamless qualities of a ‘Single Page App’, and be dirt cheap to scale. When we set out to build Percolate Studio as a static site in React, we came across a pattern that unlocks, free of any code/logic duplication, the grail:

  • Component re-use
  • Server side rendering
  • Reliable/scalable/cheap
  • Fast
  • Backend ready
  • SEO
  • Grail

Component re-use
We wanted to enable our other apps and properties to easily share code now and into the future while also adhering to the reactive UI paradigm. React is a neat reactive UI library with a simple API that we see as being compatible with the emerging web components standards.

The feature that really makes React the natural choice is its unique support for server side rendering (SSR), enabling true re-use between client and server. Most javascript UI frameworks are able to render templates to a html string on the server. This is only half the battle; when the component loads again in the browser, it must re-render itself again. If this is performed naively, and the existing markup in the DOM is simply overwritten, the user will experience all sorts of jank ranging from flashing to scroll repositioning. React eliminates this problem by allowing components on the client to ‘boot up’ from markup that has already been rendered and sent from the server (or a static page).

Reliable/Scalable/Cheap
No one wants to be roused in the dead of night to resolve an outage. Engineering a reliable scalable backend is both complicated and costly. One must account for failover, horizontal scaling, logging, alerts and an ongoing maintenance burden. It’s easy to fall into the technicians trap of over-engineering, but developers rarely factor the cost of building more than needed.

When deciding how to construct the Percolate Studio site, we needed a reliable inexpensive service that exhibited none of the maintenance pitfalls or external dependencies. The obvious choice was a static site hosted on Amazon S3 where we could spin up new machines to provide infinite scalability for as little as 50 cents per million requests.

Fast
Web engineers have spent the last twenty years optimizing to send documents more efficiently. Serving static content is a matter of leveraging learnings like caching and content delivery networks to transport content as fast as possible. Given the existing infrastructure it’s trivial to push a static site to a CDN like CloudFront. For now, we find that our site loads fast enough from S3.

A backend will execute some amount of code that results in the construction of a string of markup. This can be optimized, but the end state of these optimizations is equivalent to what you get out of the box with a static site anyway.

Backend ready
Backend as a Service (BaaS): A backend is often useful for sending emails, hitting APIs, and storing data. A familiar business requirement for our site was to send emails via our contact form. Backend as a service providers give developers the freedom to offload the thorny (and costly) concerns of maintaining a backend to a third party. So instead of spinning up our own service to solve our email challenge we relied on Mandrill to process and send mail. It’s straightforward to add rich functionality to a simple site by using third party services to achieve features that normally depend on creating your own backend.

Dynamic backends: When your site demands features not offered as a service, the site is ready to be served as an express powered Node.js app. We use a simple node server during development that is included in the package. With just a few keystrokes we’ll be able to deploy the node app and start building out rich features. Rest assured, the upgrade path is purely additive so there are no refactoring or architectural changes required.

SEO
A naive approach might be to build a simple ‘Single Page App’. By this I mean serving ‘boilerplate’ markup that is identical for every URL on the site along with the javascript containing your app, then performing all rendering on the client once the javascript has loaded. Whilst this has a lot of the same benefits of our static site approach, without as many moving parts, it also suffers from a huge problem – a measurably large negative impact on SEO, as observed by pinterest and others. Our static site doesn’t have this problem as all content is pre-rendered and served to all takers, including web crawlers. A side benefit is that the site speed will be very quick, also a documented improvement for SEO.

How

Isomorphic App Diagram The key to meeting the above benefits is to build an isomorphic app that takes a url as an input, and returns a string of markup representing the UI for that url. Furthermore, if this app is run in the browser, it should render to the DOM rather than a string. Finally, it should handle browsing to any subsequent urls on the site via client-side pushstate routing (for instant page loads). React and React-Router are the libraries we use to do the heavy lifting.

client.js
Aptly named, this client-side code begins push-state routing in the browser. The first action it performs is mapping the current route to a UI component, rendering it, and attaching it to the DOM. Because the DOM will already contain exactly the same markup (rendered either by server.js or a static file) it won’t be modified.

It’s worth noting that when React components are mounted into the DOM they begin handling events for user interaction (e.g clicking a dropdown). This is code that would never run when the component is rendered from the server.

Router.run(routes, Router.HistoryLocation, function (Handler, state) {
  var bodyElement = React.createFactory(Handler)({...});  
  React.render(bodyElement, document.body);
});

static.js
In order to generate the static files needed for S3, we interrogate all routes defined inside the ‘app’ and write a file to disk containing the markup required to render each route. Conceptually, it’s identical to the process performed by server.js for each request.

Static.js also gives us the ability to ‘smoke test’ all pages in our site and logs errors when routes throw an exception.

pages.forEach(function(page) {
  Router.run(routes, page, function (Handler, state) {
    var bodyElement = React.createFactory(Handler)({...});

    var html = React.renderToStaticMarkup(htmlComponent({
      markup: React.renderToString(bodyElement)
    }));

    StaticTools.writeHtmlPage(outputDir, page, html);
  });
});

server.js
A node app that maps the requested url to a HTML string and returns this along with client.js . We use this during development. The flow resembles:

  1. Call into ‘App’ to turn req.path into a markup string.
  2. Build and return markup for the complete page by inserting the markup generated in step 1. into the body, adding a reference to the bundled ‘App’ as a script and also adding a reference to ‘client.js’ as a script.
server.use(function (req, res) {
  Router.run(routes, req.path, function (Handler, state) {
    var bodyElement = React.createFactory(Handler)({...});

    var html = React.renderToStaticMarkup(htmlComponent({
      markup: React.renderToString(bodyElement)
    }));

    res.send(html);
  });
});

In Conclusion

We’re so pleased with the results that we open-sourced the site for others to use and give feedback or improvements. We would love to hear if you’ve built something similar.

Reference
Source code
React

Get the Newsletter

4 Comments

  1. Stephan

    Great post. I love to see how this architecture is so simple yet so efficient.
    Definitely cannot wait to try this myself.

    Btw, I noticed a typo:
    Under ‘client.js’ you write “It’s worth nothing that ..”, it should be “It’s worth noting that ..”.

    Jul 8, 2015 Reply
  2. Ginger

    Nice Stuff. Thanks for sharing

    Jun 8, 2015 Reply
  3. Emiliano Onorati

    Amazing post Zol! It was really nice to see all the possibilities that React has to offer.
    Keep up the good work!

    Apr 9, 2015 Reply
    • Tibor

      Absolulety agree, thanks for this great post and sharing the Percolate web app!

      Apr 26, 2015

Previous Post

Reactive User Interfaces

Creating maintainable interfaces with React & Meteor

Next Post

Guidelines for Animation Timing

Why great design feels right