Static Sites without the Static

A technique to get peak performance without waiting for rebuilds and preview URLs

Chris Ball
Echobind
6 min readDec 31, 2019

--

Image provided by Unsplash

Static sites, produced with tools like Gatsby, Hugo, and others, are popular for a reason. They deploy almost anywhere, are extremely fast, and don’t compromise on the developer experience. When paired with a CMS though, new sites must be built each time an editor makes an update in the UI.

This isn’t always a problem since static site rebuilds are fast, but it does mean that that tooling needs to be inserted after changes are published to perform the rebuild. Most CMS providers offer webhooks to power this functionality.

Example webhooks from Contentful

Recently, a number of services that have sprung up recently to offer “preview’ or staging versions of a site, and some that attempt to keep the entire preview client-side as part of the editor. But as good as these are, it does mean that an editor (who is often a non-technical user) needs to sit through a rebuild process to see changes they have made.

Recently, I set out to explore a technique to allow instant updates from a CMS-backed site on staging sites, while keeping the production version static. Let’s dive in and see how that technique works.

A look at the tools we’ll use

Next.js

  • Supports Server Side Rendering out of the box
  • Has simple “API” support via an /api folder. While we won't leverage it for this article, most CMS-backed sites will need to process simple forms and this is a big selling point.
  • Can optionally export a fully static site. More on this at the end of the article.

graphql-request

  • A simple wrapper for fetch to make GraphQL queries

Now (optional)

  • Provides multi-cloud redundancy out of the box
  • Supports a CDN caching layer, which we can intelligently set via Server Side Rendering

Start with the dynamic, Server Side Rendered page

First, you’ll need to setup a Next.js site. If you’re starting from scratch, I’d recommend using create-next-app.

Then, set up a GraphQL client using graphql-request:

After that, set up a Next.js page that fetches data from the CMS using GraphQL.

Fetching data for a Services page

In a staging environment, we need to point graphql-requestto our CMS's "draft" endpoint (most CMS's support this workflow). Whenever an editor saves a change to the CMS, they can reload the page to see the changes.

Remove the need to reload

Reloading the page to see updates works well, but gets tedious for the user that is previewing the changes. What if we could build in a “live reload” feature, that automatically refreshes the staging site after changes are saved? To accomplish a live reload feature, we can:

  • leverage a SSR response header to determine if page data has changed
  • add a focus hook that will asynchronously fetch the page in the background and compare the new version header

Set the version header

To set the version header, we will create a hash of the data after it is fetched during Server Side Rendering. We’ll create a utility to keep things clean.

Add hooks that implement focus and reload behavior

The first hook we will add will simplify the handling of browser focus events.

Example of a useFocus hook

And a hook that wraps useFocus that checks the version header and reloads the page if necessary:

Example of a useFocusReload hook

Here’s how to use the hooks we just put in place:

Using the useFocusReload hook and setVersionHeader util

To recap what is going on:

  1. An x-version header will be set during Server Side Render, based on the CMS API response data.
  2. When the browser receives a focus event, the current page is fetched again in the background
  3. If the header has changed, we reload the page. If not, we do nothing.

Note:
This same technique can be leveraged on any Single Page App to notify users that a new version has been released, and that they should reload the page. In that case, you would typically add a confirmation prompt before reloading on their behalf.

After deploying, you should be able to verify the x-version header was set in the browser.

(an example of the x-version header)

Moving to production on Now

Up to this point, we’ve created a Server Rendered Next.js app that will automatically reload on focus if the page data has been updated in our CMS. The final step is to bring the high performance of a static site to our page.

To do that, we will leverage a built in feature built into the Now platform called Serverless Pre-Rendering. Similar to the header approach above, we’re going to set a special cache header to cache our pages at the CDN layer. In practical terms, that means our page will be as fast as a static site for the duration that it is cached.

Now’s CDN will send a cached version of the page to the user. If the cache time has expired, it will asynchronously fetch and re-cache the page using a Serverless lambda in the background. This is awesome, because it avoids the “slow for 1 user every x seconds” problem you encounter with many caching strategies.

Let’s add a util to set the proper cache header during Server Side Rendering.

An example of a cachePageFor util

The important bits here are s-maxage and stale-while-revalidate. we set s-maxage to the number of seconds we'd like the CDN to serve a cached page, and stale-while-revalidate powers the async cache update strategy mentioned above.

To use the util, we set a cache time on a per-page basis. For example, we might want our homepage to only be cached for a max of one minute before refetching, but our contact page could be cached significantly longer.

Using the cachePageFor util

Once you deploy this code to staging, you should be able to verify that a cached response was returned:

What happens if you deploy a code update during this time?

Now will intelligently clear caches for you every time you deploy! See https://zeit.co/docs/v2/network/caching/#cache-invalidation for more detail.

Alternate approach: use Next.js’s static export

What if you can’t or don’t want to host on Now? As I mentioned earlier, Next.js also supports exporting a static site. Using this, we can change our build pipeline to export a static version for production.

Unfortunately, this requires adding some configuration to your app, especially if you have dynamic pages. Here’s an example config that filters out dynamic pages and populates sub-pages based on the results of a GraphQL query:

Fetching data and populating dynamic pages for static export

You’ll also want to add a hook from your CMS that triggers an export and deployment. Generally, it’s best to leverage a script in package.json to keep things clean.

(example of setting up a hook to build the production site statically)

You could also leverage the custom webhook to run a workflow on GitHub Actions that accomplishes the same thing.

Enjoy your non-static static site!

We now have what seems to be the best of both worlds. Our staging site acts like a traditional server that reflects changes as soon as they are saved in the CMS. Due to the CDN caching, our production site is just as fast as a static site, with the added bonus of never being out of date for longer than our cache time. We never need to wait on a site rebuild.

We’re currently rebuilding the Echobind site, which leverages this technique. Look for it to be released soon!

Thanks to ZEIT

Massive props to the team at ZEIT for not only adding this feature to the Now platform, but also for writing a blog post on Serverless Pre-Rendering that demonstrated the focus reload technique.

--

--