Fly makes it easy to deploy server-side rendered (SSR) content sites to a global fleet of servers close to your readers without a content distribution network (CDN). We’ll look at how to accomplish that with Sitepress, a Ruby site generator that can ran as a stand-alone SSR, static website, or be embedded inside of a Rails app.
Static websites have exploded in popularity over the past few years. What is it that people like so much about static site generators?
- Low operational complexity - Static websites can be deployed to a production environment without the need for a database, caching server, or other service dependencies. All that’s needed is a pile of HTML, CSS, and JavaScript files and a fast server, like nginx.
- Things break before they’re deployed - If there’s an error, its usually caught during the compilation phase by the site compiler. The only exception to this are static sites that include tons of JavaScript.
- Pretty darn fast - Static websites that keep an eye on asset sizes are generally pretty fast around the world when deployed behind a CDN, but only if those caches are warmed up. More on that later.
- Content is files - Site generators typically manage content as a bunch of text, image, JavaScript, and CSS files in folders. This means they can be stored on a file system and tracked with amazing tools like git, which bills itself as
the stupid content tracker
. Dynamic systems, like Wordpress, use databases to store content, which requires versioning schemes that are implemented at the application level.
Despite these dreamy characteristics of running static websites in production, it does come with a few trade-offs, especially if there’s a few dynamic things that are needed, like publishing content with a future publish date.
Let’s look at a few approaches for how one might accomplish such a feat so we can better understand these trade-offs.
Problem: How Do I Publish Future Content?
A common use case when dealing with content, like a blog, is scheduling a post to be published in the future. It’s a simple task for a dynamic content management system, but for static site generators, it requires some work. Here’s a few different approaches to the problem.
The Static Way: Use cron
to Build and Publish The Site
One way to publish future content with a static website is to schedule an hourly or daily task in cron
that builds all the HTML pages in the website and uploads them to a production environment. This works fine for a small website that’s not updated frequently, but it falls over for larger websites that need to publish content at more fine-grained intervals. Imagine wiring up a cronjob that runs every minute to compile and upload a website with a few thousand pages?
The Database Way: Run a CMS like Refinery or Wordpress
On the other end of the spectrum there’s the Content Management System. The most popular CMS in the world is WordPress. For Rails, the most popular CMS is Refinery according to the Ruby Toolbox.
This approach to managing content comes with a lot of complexity. For starters, these approaches require databases. Databases are hard to sync between production, staging, and development environments. Databases break. It’s difficult to version and merge conflicting data in the database if people are editing the same content. Usually some sort of caching layer needs to be built above the content generated by the database. It’s a lot of extra complexity that might not be worth it.
The Middle Road: Deploy a Semi-Static Website to Fly
Semi-static websites, also known as server-side rendered (SSR) sites, run a small server in production that renders content per request. For a blog, that means the server could check a publish_at
frontmatter key on a markdown file like this:
---
title: My First Blog Post
publish_at: January 1, 2042
---
Hello! I hope 2042 is a great year.
And quickly figure out whether or not to display the post based on the server’s current time.
We’re going to use a Ruby site generator called Sitepress to deploy a semi-static website to Fly’s global infrastructure. Why not Bridgetown, Jekyll, and Middleman? Those are all really great site generators, but they’re focused on generating static HTML, CSS, and JavaScript assets, which won’t work that great for server-side rendered sites. Outside of Ruby there’s tons of site generators, but this is written for Ruby Dispatch where we talk about all things Ruby.
How to build and deploy a semi-static website to Fly
It’s pretty quick! Here’s how to do it:
- Clone the repo https://github.com/sitepress/standalone-starter.
git clone git@github.com:sitepress/standalone-starter.git
- Install the Fly CLI and signup for an account.
- Now we’re going to run a command that provisions the app and deploys it:
fly launch --copy-config --dockerfile Dockerfile --now
- Once that finishes, run
fly open
and you should see a website that looks like this:
Hooray! You’ve published your first semi-static website. It’s running a webrick server that renders everything per-request.
How fast is the website we just deployed?
Now let’s see what things look like for people on the other side of the world by running fly curl
, a nifty little tool that loads your website from various Fly outposts.
fly curl https://$YOUR_SITE_NAME.fly.dev
REGION STATUS DNS CONNECT TLS TTFB TOTAL
ams 200 0.7ms 0.8ms 29ms 193.5ms 195.1ms
cdg 200 0.7ms 0.9ms 29.1ms 179.5ms 181.1ms
dfw 200 0.7ms 0.9ms 29.5ms 71.3ms 73.5ms
ewr 200 0.5ms 0.7ms 25.1ms 90.6ms 91.8ms
fra 200 0.6ms 0.8ms 30.5ms 204.7ms 205.9ms
hkg 200 0.6ms 0.7ms 21.7ms 186.9ms 188.5ms
lax 200 0.5ms 0.7ms 21.9ms 50.5ms 51.2ms
lhr 200 0.8ms 1ms 29.2ms 174.3ms 175.9ms
mia 200 0.8ms 1.1ms 33ms 116.4ms 160.6ms
nrt 200 0.4ms 0.5ms 17.4ms 135.8ms 136.5ms
ord 200 0.6ms 0.8ms 22.8ms 93.3ms 95.2ms
scl 200 1.1ms 1.5ms 48.3ms 211.3ms 213.3ms
sea 200 0.4ms 0.5ms 16.3ms 57.4ms 57.9ms
sin 200 0.5ms 0.6ms 17.5ms 204.6ms 205.2ms
sjc 200 3.3ms 3.4ms 34.9ms 46.1ms 46.4ms
syd 200 0.7ms 0.9ms 24.9ms 192.9ms 193.6ms
yyz 200 0.6ms 0.8ms 31.6ms 99ms 99.9ms
The time-to-first-byte (TTFB) times on the other side of the world probably aren’t all that great, which means people trying to read your website are sitting there waiting. TTFB is the amount of time people have to wait after typing your website.com
into their browser and receiving the first bytes of HTML.
Provision and deploy to servers around the world
Let’s fix that problem by deploying this website closer to them, without a CDN, by telling Fly we’re cool running our site in these regions:
fly regions set sin fra ord
Region Pool:
fra
ord
sin
Backup Region:
This command tells Fly the parts of the world you want to deploy the website, but the servers aren’t running there yet. Let’s spin them up.
fly scale count 3 --max-per-region=1
Fly will scale up your semi-static site in each of these regions so they’re ready to respond to requests as they come in.
Let’s see how those regions scaled up.
fly status
App
Name = $YOUR_SITE_NAME
Owner = personal
Version = 2
Status = running
Hostname = $YOUR_SITE_NAME.fly.dev
Platform = nomad
Instances
ID PROCESS VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED
394677de app 2 ord run running 1 total, 1 passing 0 1m6s ago
4de70730 app 2 sin run running 1 total, 1 passing 0 1m6s ago
8f6646aa app 2 fra run running 1 total, 1 passing 0 2m21s ago
Now how fast is our website?
Let’s run fly curl
again and see what happened to the latency:
fly curl https://$YOUR_SITE_NAME.fly.dev
REGION STATUS DNS CONNECT TLS TTFB TOTAL
ams 200 0.7ms 1ms 29ms 37.7ms 39.4ms
cdg 200 0.9ms 1.1ms 22.9ms 44.5ms 45.9ms
dfw 200 0.8ms 1.2ms 25.9ms 68.1ms 69.3ms
ewr 200 6.8ms 7.1ms 85.5ms 70.3ms 71.3ms
fra 200 0.8ms 1.1ms 26.9ms 33ms 34.4ms
hkg 200 0.9ms 1.1ms 22.7ms 70ms 71.7ms
lax 200 0.5ms 0.7ms 21.7ms 86.4ms 87.1ms
lhr 200 0.8ms 1.1ms 24.5ms 36.4ms 38ms
mia 200 0.8ms 1.2ms 25.3ms 69ms 70.8ms
nrt 200 0.4ms 0.6ms 18.1ms 94.1ms 94.6ms
ord 200 0.4ms 0.6ms 21.8ms 51.9ms 53ms
scl 200 1.1ms 1.4ms 39.8ms 168.1ms 170.3ms
sea 200 0.4ms 0.5ms 16.1ms 83.2ms 91.6ms
sin 200 0.4ms 0.5ms 17.4ms 26.3ms 26.9ms
sjc 200 0.4ms 0.5ms 28.1ms 83.6ms 83.7ms
syd 200 0.9ms 1.1ms 23.4ms 113.7ms 115.1ms
yyz 200 1ms 1.5ms 41.8ms 60.8ms 62.3ms
Much better! The TTFB time decreased, which means people who are reading the website see the content instantly (under 250ms) as far as they’re concerned.
Fly, Content Distribution Networks, Your Customers, and You
With Fly, it is possible to cut out the middleman CDN and simply run content servers closer to your customers. CDNs are great, but they do add latency to your application for a few reasons:
- A CDN with a cold cache has to fetch the content from the origin. This adds latency to the initial request.
- When the cache from a CDN expires, it has to check the origin for a fresh resource which again, adds latency to the request. Some CDNs can be configured to serve up the stale content while requesting the new stuff from the origin, but nobody wants something that’s stale. If people like stale stuff bakeries wouldn’t mark down day-old-bread.
For static websites, this isn’t a huge deal, but for mixed websites where all application requests go through the CDN and the cache times are low like a blog or news website, its at least an extra hop that simply isn’t necessary when running servers close to your users.
There’s lots of reasons to deploy semi-static websites
Here’s a few that you could try for your own projects:
- Always up-to-date project README files - Lots of open source projects have a website that repeat content from the
README.md
file at the root of their project. With a semi-static site, its easy to fire off an HTTP request for an open-source projects latestREADME.md
file and render it within the project website. The starter app shows how a Github README could be rendered on a project page. - Localization - A semi-static site could localize content depending on the
FLY_REGION
or users IP address. - Treat Your Content As Data - Imagine a world where you could get a list of relevant help articles to your customers from within your application? It’s possible with Sitepress via a line of code that looks like
HelpPages.tags(:login, :security, :account_management)
. Treating content as data opens up a lot of interesting use cases for tightly integrating content with your app, which can make for a great experience for your customers. - Community Websites - Git is the ultimate content management system for communities, especially when it’s backed with workflows like “Open a PR to edit content”. It’s how Fly’s docs are managed. Be sure to include an “Edit with Github” link on the page that opens the content up in Github’s edit view so people can make contributions for quick edits, like typos.
- Run it in your Rails Apps - If you’re a small team or solo developer, Sitepress can be embedded in your Rails apps and integrate directly with your routes files. If the Rails application has a database, you’ll want to read about how to run ordinary rails apps globally.