It feels as thought static site generators are experiencing a meteoric rise in popularity amongst developers. In reality, we're only at the very beginning of a paradigm that will continue to take off for years to come. The rise of Gatsby, Next.JS, and Hugo will undoubtedly create opportunities for fortune in the coming years, and it's hard to argue that anybody is better positioned for this than Netlify.

It's hard to overstate the significance Netlify has in the JAMStack ecosystem.  When it comes to static site hosting, Netlify is the undisputed frontrunner; their service is simply the easiest and fastest way to get Static sites live. I'd love to have been paid to express that opinion, but I'm an unaffiliated Netlify costumer. In a way, I suppose I'm paying Netlify to endorse them, which makes me a horrible business person.

Anyway, we're here because we share a common addition: the need for speed. Static sites are fast. The more we use and appreciate the quick loads times of static websites, the more we crave our next fix of even faster load times. Today we'll be diving deep into Netlify's performance tuning optimizations to feed our insatiable thirst for speed. We're going to uncover some of the lesser-known ways to crack out our site, but let's briefly touch on the benefits Netlify gives us out-of-the-box.

Netlify Edge: Global CDN for Hosting & Redirects

Netlify comes hot out the gate by offering a free CDN dubbed Netlify Edge. Sites deployed to Netlify are immediately distributed across a global network, with zero configuration.

A Netlify representative briefly went into details about this on HackerNews a while back:

Our CDN is global yes. Actually, when you deploy to netlify, we'll fingerprint all your assets (CSS, JS, images) and rewrite the URLs in your HTML so we can serve those straight out of Akamai's CDN with a 1 year max-age. We run our own CDN for the actual HTML files, currently we have edge nodes in 7 datacenters around the work. This CDN is super optimized to this use case, which means you'll get a 90%+ cache hit rate on your HTML from our cache servers, while still getting instant cache invalidation whenever you push a new deploy. This is also what allow us to do more complex things like proxying, OAuth1 request signing and pre-render support directly at the CDN level. We currently beat all the services we run regular benchmarks again in our internal benchmarks for real user performance.

Utilizing Netlify's CDN is as simple as pointing your domain to their nameservers, as you would with Cloudflare (or an equivalent service):

Netlify's DNS configuration

Domains pointing to Netlify automatically receive SSL certificates via Let's Encrypt. It's a nice freebie:

SSL via Let's Encrypt

Upgrading Node and NPM

Sites living on Netlify are deployed as Docker containers, typically running Ubuntu 16.04. Netlify has silently been offering more control over customizing these images to give us the power to customize the environment we're running. The build images Netlify offers can be found in a public repo here.

Netlify's default Docker image ships with Node v8.X. Luckily for us, Netlify has reserved an environment variable which we can use to upload the version of Node and NPM in our container! The easiest way to accomplish this is by setting these variables via the Netlify UI:

Setting Environment Variables

Yes, that's really all it takes! The versions we specify for NODE_VERSION and NPM_VERSION will be installed on our container the next time we create a build.

NPM_TOKEN is another useful variable for installing private NPM packages, like Fontawesome.

The next time your site builds, the deploy log will show that your newly specified versions were recognized and installed:

5:17:08 PM: Starting build script
5:17:08 PM: Installing dependencies
5:17:09 PM: Started restoring cached node version
5:17:12 PM: Finished restoring cached node version
5:17:12 PM: Attempting node version '10' from .nvmrc
5:17:13 PM: v10.16.3 is already installed.
5:17:14 PM: Now using node v10.16.3 (npm v6.9.0)
5:17:15 PM: Attempting ruby version 2.6.2, read from environment
5:17:16 PM: Using ruby version 2.6.2
5:17:17 PM: Using PHP version 5.6
5:17:17 PM: Started restoring cached node modules
5:17:17 PM: Finished restoring cached node modules
5:17:17 PM: Found npm version (6.9.0) that doesn't match expected (6.11.3)
5:17:17 PM: Installing npm at version 6.11.3
5:17:26 PM: /opt/buildhome/.nvm/versions/node/v10.16.3/bin/npm -> /opt/buildhome/.nvm/versions/node/v10.16.3/lib/node_modules/npm/bin/npm-cli.js
5:17:26 PM: /opt/buildhome/.nvm/versions/node/v10.16.3/bin/npx -> /opt/buildhome/.nvm/versions/node/v10.16.3/lib/node_modules/npm/bin/npx-cli.js
5:17:26 PM: + npm@6.11.3

Minify Static Assets

Netlify gives us the option to optimize frontend assets as part of every production build. As a part of every deployment, Netlify optimizes our build by:

  • Handling the compression and bundling of JS & CSS
  • Compresses images in our build using lossless compression
  • Cleans URL routes to omit file extensions

The easiest way to enable asset optimization is via the UI:

Netlify Asset Optimization

Alternatively, we can accomplish the same effect by adding a Netlify config file to the home of our project called netlify.toml.

Fine-tuning Netlify via netlify.toml

The configurations we've handled via Netlify's UI can alternatively be handled in netlify.toml. netlify.toml actually allows for even more optimization, so it's important to get a decent grasp on what we can accomplish with this, Here's an example:

# Settings in the [build] context are global and are applied to all contexts, unless otherwise overridden.
[build]
  command = "gatsby build"
  publish = "public/"
  functions = "functions/"

# Expects incoming webhook from my Ghost CMS
[template]
  incoming-hooks = ["Ghost"]

# A redirect rule which routes Wordpress admin attempts to an offensive external URL.
[[redirects]]
    from = "/wp-login.php"
    to = "https://i.imgur.com/1Ia9tTG.gif"
    status = 301

# A simple redirect rule to accomodate for a page name which has changed 
[[redirects]]
    from = "/creating-endpoints-with-lambda/"
    to = "/create-a-rest-api-endpoint-using-aws-lambda/"
    status = 301

# Role-based redirects does not have a 'to' property.
[[redirects]]
  from = "/secret_admin"
  status = 200
  conditions = {Role = ["admin"]}
  force = true

# Headers to return with requests for the specified paths
[[headers]]
  # Define which paths this specific [[headers]] block will cover.
  for = "/*"

  [headers.values]
    X-Frame-Options = "DENY"
    X-XSS-Protection = "1; mode=block"

    # Multi-key header rules are expressed with multi-line strings.
    cache-control = '''
      max-age=31557600,
      no-cache,
      no-store,
      must-revalidate'''

# Asset optimization, mimicking what we accomplished earlier
[build.processing]
  skip_processing = false
[build.processing.css]
  bundle = true
  minify = true
[build.processing.js]
  bundle = true
  minify = true
[build.processing.html]
  pretty_urls = true
[build.processing.images]
  compress = true
netlify.toml

There are three things here worth focusing on for optimization purposes:

  • [[redirects]]: Netlify makes it very easy to set redirect routes for pages that have gone missing. In addition to this, a redirect can also be used to restrict access to certain pages of our site to users who match a specific role. The ease-of-use associated with created redirects is especially helpful if we pair this with Netlify's analytics offering, which we'll cover in a bit.
  • [[headers]]: Setting response headers is (obviously) important for a lot of reasons, but one in particular is for the appeasement of Google's Lighthouse speed tests. In their own words, "HTTP caching can speed up your page load time on repeat visits. When a browser requests a resource, the server providing the resource can tell the browser how long it should temporarily store or cache the resource." The headers we've set in the above example satisfies this.
  • [build.processing]: If you'd prefer to handle configuration of asset optimization in a config file, more power to you.

Netlify Analytics

Netlify offers a surprisingly good analytics dashboard service called Netlify Analytics, which has numerous benefits for site performance and optimization. That said, this product is not free which is understandably unacceptable for a large contingency of people. I'm not here to sell you services, but Netlify Analytics has numerous performance/optimization benefits for static sites, which we should not ignore.

Unlike industry-standard analytics such as Google Analytics, Netlify Analytics adds zero overhead to page loads. In their own words:

Netlify Analytics will never impact the render time of your web applications since the work of capturing requests happens on our infrastructure, not in the browser.

As a comparison, here's the overhead my other analytics scripts are costing me:

Third-Party
Size
Main-Thread Blocking Time
137 KB
263 ms
36 KB
56 ms
49 KB
39 ms
39 KB
15 ms
300 KB
0 ms
1 KB
0 ms

Google Analytics alone adds 340KB to our page load, 301KB of which are unsolicited auxiliary scripts. That's so absurd that Google itself raises this is a critical problem. That's right folks, the above information came from my site's Lighthouse audit. There's something beautiful about Google products auditing other Google products and concluding that 300KB of unwanted surveillance is unacceptable:

Google hates Google

Finding and Fixing 404 Errors

Perhaps the best feature of Netlify Analytics is a report of pages users are receiving 404 errors for. We can quickly define pages on our site which has disappeared or been renamed as users frequently fail to load said pages:

Netlify Analyics' "Not Found" report

Thanks to netlify.toml, it's very easy to fix these issues by defining redirects to proper pages. We can also see hilarious hacking attempts as users try to hit common admin pages that don't exist. I recommend redirecting this traffic to a special place in hell.

Speeding Up Build Times

Gatsby build times quickly get out of hand as your site inevitably grows larger. It's not the end of the world, but waiting 10 minutes just to create a build can get annoying really fast. One way we can mitigate this is by using a Gatsby plugin called gatsby-plugin-netlify-cache. This plugin helps to reduce load times by only building files that have changed since the last build. Install like so:

npm install gatsby-plugin-netlify-cache --save

The only change we need to make to gatsby-config.js is add gatsby-plugin-netlify-cache to our list of plugins. Now when we create a new build, we'll see this in the logs:

8:38:41 PM: plugin-netlify-cache: Restoring 624 cached files for public directory with 0 already existing files.
8:38:44 PM: plugin-netlify-cache: Restoring 45 cached files for .cache directory with 2 already existing files.
8:38:44 PM: plugin-netlify-cache: Netlify cache restored
Netlify Deploy Summary

Final Thoughts

For a hands-off hosting solution, Netlify allows us to have an impressive amount of control over how we fine-tune our static sites. While I've spent this time highlighting the benefits of Netlify as a service, I am legitimately concerned about Netlify's lack of competition. Netlify recently rolled out a "feature" where teams can view their past build history on a new "build" tab, which was announced as such:

We’re excited to introduce the new Builds tab to the Netlify dashboard, designed to give your team deeper insights into the powerful CI/CD functionality of the Netlify workflow. Now you can:

  • See a centralized view of builds across your team and sites at a glance
  • View the number of builds currently in progress or pending
  • Investigate which builds succeed, which builds fail, and how long it takes for each build to deploy
  • Dive deeper into the deploy summaries and logs for each build in one click


In reality, Netlify was actually releasing a way to charge users for exceeding a newly introduced "build minutes" limit. Netlify had previously provided build services for free. This changed without warning, and many customers suddenly found their sites held at ransom with the risk of suspension. The statement reads:

You are on the free Netlify Starter plan, which includes 300 minutes of build compute time per month. That’s quite a lot—more than you and the vast majority of our free users are currently consuming. We’ve always provided generous features in our free tier, including custom domains and deploy previews, and you’ll see our approach to build minutes is no different.

Gatsby builds routinely take up to 10 minutes. 300 minutes is not "quite a lot" by any stretch of the imagination. I've never needed to monitor monthly "build minutes" before,  nor has anybody else in history; I'd wager. The allure of sites on the JAMStack is constant automatic builds. If a build is triggered every time an author on our site corrects a spelling error, we're looking at hundreds of builds per month.

All that said, I'm clearly still a fan of Netlify (I don't write long-winded posts about products I dislike). There are worse things than being locked into Netlify, and hopefully you've found something in this post to make your site is a little better.