Over the last few months, I’ve tried out a few different hosts for this blog (a static site generated with Hugo). After starting off with a basic Google Cloud Storage bucket, I migrated to Netlify and then Vercel. After Vercel, I migrated back to Netlify for a little while before moving to Cloudflare Workers Sites, which ended up resulting in a 2x increase in performance! In this post, I’ll share my experience with hosting a static site on Cloudflare Workers Sites.

First off, I want to say that Netlify and Vercel are both excellent products. The only reason that I moved to Workers Sites is because I’m a huge Cloudflare fan who’s interested in trying out the latest technologies. I’ve used Cloudfare DNS since I started blogging. For my work in blockchain, I use Cloudflare’s WAF, Argo Tunnel, and load balancer to maintain a secure and scalable fleet of nodes. Migrating this blog to Cloudflare was just the next step in this love affair.

What is Cloudflare Workers Sites?

Before we get into how I migrated to Cloudflare Workers Sites, let’s talk about what it is first. Cloudflare Workers is a serverless platform that lets you run JavaScript code directly on Cloudflare’s global network of data centers. Workers allows you to build low-latency websites, apps, and APIs because user requests are automatically routed to the nearest data center.

Check out the screenshot below. I’m writing this post half an hour north of Boston, MA right now. According to Cloudflare, my network latency is only 20ms. Cloudflare has over 150 data center locations scattered around the world, so you should be able to get great performance no matter where you are.

20ms latency from Boston, MA to Cloudflare's network.
20ms latency from Boston, MA to Cloudflare's network.

Cloudflare Workers are able to access a globally distributed persistent storage solution called Workers KV. Workers KV is a key-value data store that’s optimized for high read volume. It’s a perfect lightweight storage option for apps and other services running on Cloudflare Workers.

Long story short, it turns out that Workers KV also works for storing static sites generated by Hugo, Gatsby, and other SSGs. This makes complete sense if you think about it. When you access a static site, you point your browser to a unique URL, and the server returns a pre-generated static HTML file. In this model, the URL is the “key” and the contents of the HTML file is the “value”. Thus, a static site can be stored in a key-value data store like Workers KV.

Check out the screenshot of my Workers KV namespace below. You can see the URL 100-day-blogging-challenge/index.4631f279d2.html “key” on the left and the corresponding HTML document “value” on the right.

How to Deploy a Cloudflare Workers Site

Moving from Netlify to Cloudflare Workers Sites is super easy. First, you’ll need to make sure you’re subscribed to Cloudflare’s “Workers Bundled” plan, which starts at $5/month. For that very low price, you get 10 millions requests and 1 million KV reads/writes per month, along with 1 GB of KV storage. This should be more than enough for 99% of static sites out there.

After signing up for the Workers Bundled plan, install Wrangler with npm.

npm install -g @cloudflare/wrangler

Next, configure an API token in your Cloudflare dashboard. Be sure to choose the “Edit Cloudflare Workers” token template.

Create an API token for your Cloudflare Workers Sites.
Create an API token for your Cloudflare Workers Sites.

Next, run the Wrangler configuration command and supply your API token.

wrangler config

That’s it for basic configuration! Now, create a site with the command below.

wrangler generate --site your-site-name

Finally, publish your site with the command below.

cd your-site-name && wrangler publish

Integrating Hugo with Cloudflare Workers Sites

I use Hugo to statically generate this blog, but this section is applicable for Gatsby, Jekyll, and other static site generators as well. To deploy your static site, you’ll need to make a few changes to your wrangler.toml configuration file.

name = "brianli-com"
type = "webpack"
account_id = "your-account-id"
workers_dev = false
route = "brianli.com/*"
zone_id = "your-zone-id"
site = { bucket = "./public" }

The important settings to pay attention to are account_id, workers_dev, route, zone_id, and site.

  • account_id - your Cloudflare account ID.
  • workers_dev - specifies whether you want to deploy to a registered workers.dev domain.
  • route - the domain you want to serve your site from.
  • zone_id - your Cloudflare zone ID.
  • site - the directory of your exported static site.

For my specific setup, I ended up moving my Hugo project contents into my Workers Sites project folder like so…

brian@bl-projects:~/brianli.com$ ls
README.md   config.yml  layouts       resources     wrangler.toml
archetypes  content     netlify.toml  static
assets      dist        public        workers-site

With this setup, running hugo --gc --minify will export my Hugo site to the public folder. After that, the exported site can be deployed to Cloudflare with wrangler publish. To automate the publishing and deployment process, I also set up a Flask endpoint on a free Google Compute Engine VM. After a successful push to my site’s GitHub repo, a POST request is sent to the Flask endpoint (which, coincidentially, is exposed to the public Internet via Cloudflare Argo Tunnel). After the POST request is received, Flask runs the local bash script below to build and deploy the site.

!/bin/bash
git pull
hugo --gc --minify
wrangler publish

The entire deployment process takes less than 25 seconds (on a single-core VM), which is 2-10x faster than deployments on Netlify and Vercel – probably because the presence of an always-on server saves a ton of time.

What About Performance?

Now we can get to the fun part – performance! If a site is properly optimized, the single biggest factor that impacts page load times is the distance between a visitor and the server. This is why CMS-based sites built with WordPress and Ghost are typically slower than fully static sites deployed to the network edge. When you deploy a site using Cloudflare Workers Sites, the entire Cloudflare network, with over 150 data centers around the world, effectively becomes your origin server. In comparison, Netlify’s global CDN only conists of 6 locations for free users and 12 locations for enterprise plans.

Cloudflare vs. Netlify PoPs.
Cloudflare vs. Netlify PoPs.

Even if you put Cloudflare in front of Netlify, it’s still not going to be faster than deploying straight to Cloudflare unless you can ensure your entire site is cached in all locations at all times – a monumental undertaking. Consider what happens when someone makes a request to an uncached page on your Netlify site behind Cloudflare. Since Cloudflare is a proxy, there is an additional roundtrip request to grab the uncached page from Netlify.

Visitor -> Cloudflare -> Origin -> Cloudflare -> Visitor

On subsequent requests to the same page, the request behavior will change to the flow below because Cloudflare can serve the page directly from its cache. This is location-dependent of course. In other words, if another visitor living across the world requests the same page, there’s no guarantee that it will be served from Cloudflare’s network.

Visitor -> Cloudflare -> Visitor

For a Cloudflare Workers Site, the request path is always…

Visitor -> Cloudflare -> Visitor

This means you’re able to utilize Cloudflare’s entire network as a distributed and permanent origin layer instead of as a proxy layer with a temporary cache.

To demonstrate the difference between Cloudflare Workers Sites and Netlify (without Cloudflare caching), I performed a TTFB (time to fist byte) test from 10 locations with KeyCDN’s testing tool. For both sites, I ran the test three times and recorded the average time as well.

The results were striking!

As you can see in the table below, Cloudflare beat out Netlify in 9/10 locations. In Frankfurt, Amsterdam, Singapore, and Tokyo, Cloudflare was faster by over 200%. The only location where Cloudflare was slightly slower was Bangalore. If you’re hosted on Netlify, it may be worth experimenting with Cloudflare Workers Sites – especially if you’re obsessed with site speed like me.

Cloudflare 1
Netlify 1
Cloudflare 2
Netlify 2
Cloudflare 3
Netlify 3
Cloudflare Avg.
Netlify Avg.
CF Speed Diff
Frankfurt89.65 ms289.02 ms118.87 ms470.16 ms101.63 ms264.11 ms103.38 ms341.10 ms229.93%
Amsterdam97.20 ms263.88 ms66.17 ms312.30 ms108.25 ms297.63 ms90.54 ms291.27 ms221.70%
London94.89 ms262.11 ms121.00 ms299.08 ms86.61 ms299.10 ms100.83 ms286.76 ms184.39%
New York73.84 ms162.33 ms164.17 ms168.06 ms67.67 ms159.77 ms101.89 ms163.39 ms60.35%
Dallas145.33 ms335.03 ms170.53 ms271.26 ms72.16 ms303.22 ms129.34 ms303.17 ms134.40%
San Francisco
195.99 ms184.59 ms167.56 ms159.58 ms92.70 ms269.26 ms152.08 ms204.48 ms34.45%
Singapore63.83 ms320.63 ms223.31 ms325.84 ms47.61 ms413.27 ms111.58 ms353.25 ms216.58%
Sydney48.05 ms377.42 ms291.14 ms358.84 ms68.87 ms324.07 ms136.02 ms353.44 ms159.85%
Tokyo73.67 ms564.58 ms383.79 ms613.90 ms120.86 ms589.33 ms192.77 ms589.27 ms205.68%
Bangalore501.68 ms495.44 ms499.92 ms421.00 ms495.25 ms517.03 ms498.95 ms477.82 ms-4.23%

Summary

So far, I’m very happy with the move from Netlify to Cloudflare Workers Sites. Over the next few weeks, I’m going to continue tweaking my publishing and deployment process. I’m considering dropping the always-on server on Google Cloud Platform and moving to a GitHub Actions-based build process instead. I also plan on sharing how I implemented redirects (over 700 of them) with Workers KV. If you’re interested in or already hosting your static site on Cloudflare Workers Sites, let me know! I’d love to chat and compare notes.