Blazing Fast Page Load with Static Rendering

Stephen Cook

How quickly a user sees something when your website loads is invaluable. Studies from 2006 found that most users will abandon your site if it hasn’t loaded in 3 seconds. Unsurprisingly, users haven’t become more forgiving in the last 13 years.

Unfortunately, modern web frameworks like React are pretty bad with page load speed out of the box.

But all is not lost, you don’t need to drop React to have fast page loads. There’s lots you can do to speed up your page load — this post focuses on Static Rendering, a variant of Server Side Rendering.

What is Server Side Rendering?

Static Rendering is basically Server Side Rendering done without a server. So let’s first of all explain what Server Side Rendering is.

A vanilla JavaScript application cannot render anything until the browser has downloaded the JavaScript bundle, parsed it, and started executing it.

Diagram showing a mobile screen stays blank until JavaScript is parsed

This means that there’s a long period where the user is just looking at a blank page. Users don’t like looking at blank pages — they have places to be, memes to enjoy.

Meme: one does not simply wait 3 seconds before leaving to go read memes

It also means that search engines will struggle to crawl your site, since they often assume that they can ignore a lot of JavaScript. Advanced web crawlers like Google’s are getting better at loading JavaScript, but generally giving web crawlers a hard time isn’t a good idea.

Server Side Rendering is where your server also executes the JavaScript application. This way, it can give your browser the JavaScript application to run (like before), but also the HTML prefilled with your application’s initial state.

Diagram showing server responding with more HTML (prepared on the fly), so mobile screen stays blank only until HTML is returned

This means that while your user’s browser is downloading the JavaScript, they are already looking at the app. No more looking at blank pages.

What is Static Rendering?

Static Rendering is basically just pretending you’re doing Server Side Rendering, but doing it all at build time. In other words, when bundling your app you run a local server, check what all the HTML is, and then put that HTML somewhere.

Diagram showing server responding with more HTML (prepared ahead of time), so mobile screen stays blank only until HTML is returned

Compared to traditional Server Side Rendering, this has a few downsides:

  • no knowledge of user-specific data (we don’t know any users at build time)
  • we need to explicitly specify what pages to statically render

but also a few upsides:

  • run-time is faster (since it’s all done at build time)
  • we don’t need to deploy a server to run our JavaScript

How Do You Statically Render?

To make our lives at Thread easier, we created a tool to do this static rendering automatically for us, alongside our normal webpack build. We open-sourced it, so feel free to give it a spin: @teamthread/react-static-render-plugin.

To use it, we take our old React Router code:

ReactDOM.render(
  <Router>
    <Route path="/" component={Index} />
  </Router>
);

and we replace it with:

import { render } from '@teamthread/react-static-render-plugin/render';
 
export default render(Router => (
  <Router>
    <Route path="/" component={Index} />
  </Router>
));

and then we add a new plugin to our webpack config:

const StaticRenderPlugin = require('@teamthread/react-static-render-plugin');
 
module.export = {
  // ...
  module: {
    rules: loaderRules,
  },
  plugins: [
    // ...
    new StaticRenderPlugin({
      pages: {
        'index-signed-in': {
          path: '/',
          locals: { signedIn: true },
        },
        'index-signed-out': {
          path: '/',
          locals: { signedIn: false },
        },
      },
    }),
  ],
};

and we’re done! Webpack will now build us a index-signed-in.html and index-signed-out.html, alongside our normal build assets.

Progressive Rendering

The next challenge we have is that we want to render some components differently when static rendering. For example, we have a component that encourages you to sign up to Thread by offering you a discount.

The discount amount, however, is actually a dynamic value provided by the server. We don’t know what it is at build time.

Fortunately, our plugin sets a global STATIC_RENDER environment variable, so we can replace:

<li>Register to get {amount} off if you order within 3 days</li>

with:

<li>
  Register to get {process.env.STATIC_RENDER ? '' : amount} off if you order
  within 3 days
</li>

Generally, we want our statically rendered app to be a watered down version of our full app. By gracefully adding more to the app when going from the statically rendered version, and the full version, we can make the whole page load feel very smooth to the user.

Ideally, the user shouldn’t realise that any transition happened at all.

Summary

To recap, we use Static Rendering at Thread. If you load thread.com with JavaScript disabled, you will still see a large portion of the site load.

We used @teamthread/react-static-render-plugin, and you can too — give it a spin!

If you have any questions, feel free to ping me on Twitter @StephenCookDev. And if you’re looking for a job, check out our jobs page and get in touch! 🙂