Building Static Sites with React

Introduction

This article explains how to scaffold a website using Create React App, hook it up to content from a CMS and deploy as a static website on AWS S3.

Why React?

React has popularized component based development and risen to dominate the front end space with an intuitive API for building applications. It boasts a huge community and has an excellent developer experience.

Why Static?

React is traditionally used for building client side applications, which are burdened by an upfront performance cost. For websites that do not require a single page app experience, it is irresponsible to make a user pay this UX penalty.

React has more recently introduced server side rendering as a great solution to this problem, however this can require a complex Node server architecture, which may not be appropriate for websites with more basic needs.

We can achieve the same result of sending pre-rendered markup to the browser with a technique called static rendering (also known as snapshot rendering). This is where the pages are rendered into HTML by a build process and then written to static HTML files. This gives us the performance boost without the complex server maintenance. This article will explain one such build process.

Static sites are better for Google indexing and can mitigate security threats by way of not having a database behind the page rendering.

Starting Point

We will start with a basic client rendered app, built from Create React App. Run this command in your terminal:

$ create-react-app static-react

This will install a React application into a new directory 'static-react'. We can cd into this new directory and yarn start to see it in our local browser.

Set up routing

Next we need to add some pages, so we can set up routing. Stub out some basic pages with new files Home.js and Post.js

Install React Router:

$ yarn add react-router-dom

Update App.js with the relevant React Router components, plus the new page files we previously created.

Deploy to S3

This section will briefly describe how we can deploy our website to Amazon S3 using Travis CI. You can choose to use an alternate CI service, such as Bitbucket Pipelines, or just skip the deployment step and follow on with this tutorial using your localhost.

You will need to create a free account with Travis CI which you can do using your Github login. Next we create a .travis.yml file with the following config:

The last two lines need to be uncommented and updated with the details from your S3 bucket, for example:

bucket: "static-react-website-tutorial"
region: ap-southest-2

Initialize the project as a Git repo and commit the files. Create a new repo on Github and push up the initial commit. We must then sync Travis CI with our Github account to pick up the new repo. Now, every time we push a new update to Github, Travis will yarn build then deploy the compiled code to S3.

We can now view the site at http://your-bucket-name.s3-website-your-region-name.amazonaws.com. If we open the dev tools and select the "Network" tab, we can record a timeline of the page render. Since it is important to optimise for 'mobile first', we will select "Fast 3G" from the network speed presets, then record the page load.

Performance profile of client rendered app with Fast 3G settings

The above screenshot shows that nothing renders until 1.50s, with the complete render clocking in at 2.09s. We should keep in mind that this website has a bare minimum of content, so it is taking 2 seconds to render hardly anything. In the next section, we will improve this with static rendering.

Set up Static Generator

Install the react-snap package

$ yarn add react-snap

Update the build script in package.json to run react-snap after the app build

Update index.js to use the React 16 hydrate() method

We can now push the updates to Github and trigger another build and deployment. Then we can rerun the page load performance test with dev tools.

Performance profile of static build with Fast 3G settings

We can see that our first paint time has dropped from 2.09s down to 719ms - which is roughly one third of the time the client rendered page took. Or, a 3x speed increase! We can also load the pages with JavaScript disabled - this is rarely a hard requirement, but nevertheless it serves as a useful benchmark for what is independent of JS.

Set up CMS

This section will describe how to use Contentful as a headless CMS for your static React website. Let's begin by installing their JavaScript API package.

$ yarn add contentful

Next, create an account with Contentful, set up a new space, and then navigate to where it displays your API keys for the space. Create a .env.local file and add to it the "space ID" and "access token".

REACT_APP_SPACE_ID=<your_space_id>
REACT_APP_ACCESS_TOKEN=<your_access_token>

Then create a handful of content items so we can consume them in our site.

Next, we update our Home.js file with a fetch to the Contentful API for the posts we just created. The updated file is now this:

When we run this in our browser, we'll see the object for each entry in our console, as shown below. We can inspect this data structure to determine which object keys to use for displaying the relevant data.

Console log for each post object

Let's now make each of these entries link to their respective pages. Update Home.js with React Router's Link. This is the diff to the previous snippet:

We now need to update the Post route in App.js accordingly, and also remove the placeholder links. The diff for this is:

We should check these links are working in the browser - click each of the links rendered on the homepage and we should see the same placeholder content loading for different URLs, each containing the post id.

Next, we will wire up the content for Post.js. We will import the Contentful client and make a fetch to their API using the post ID from the URL, which we can access via React props this.props.match.params.id. The updated file is shown below:

Again, we start by logging the data object to the console to see what we get back from the API:

Console log of the data object for a post

Now, let's update the returned markup with the post content. The diff for this is:

Metadata

A critical part of static web pages is the presence of metadata such as the <title> and <meta> attributes for SEO and social sharing. Create React App does not include any features for this out of the box, so we will introduce the popular React Helmet package to manage our metadata.

$ yarn add react-helmet

Let's start by updating Post.js to render a <title> attribute containing the post title. The diff for this is:

Markdown Support

Contentful supports Markdown syntax out of the box, so in order to take advantage of this feature, we need our React templates to support rendering Markdown. Firstly, let's add some Markdown to one of our posts in the CMS, for example:

Example Markdown content in CMS

In this tutorial we will use the marksy package, a library for converting markdown into a virtual DOM structure.

$ yarn add marksy

We can now update Post.js with a helper function for compiling the raw Markdown into a VDOM structure to output inside our JSX. The diff for this is:

Conclusion and Starter Repo

We have covered a lot of ground in this tutorial, taking a basic client rendered Create React App installation from its default state to a CMS-powered static website with Markdown and metadata support.

To make life a little easier, I have created a starter repo including all the packages featured in this tutorial, configured to generate a static website rendering the example content from Contentful's blog content structure. You can find this on Github here: https://github.com/astrotim/contentful-static-react. Please feel free to submit bug reports or feature requests via a Github issue.

Now go forth and create static!