What are Static Sites
Static sites are sites comprised of and served from static files. Every HTML, CSS, and JavaScript file already exists and is sitting on a file system, somewhere, waiting to be served to the end user.
This is in contrast to dynamic sites which generate the pages on demand, returning different content based on the nature of the request, records from a database, etc. This may sound limiting, and in some ways it is, but those limitations can do wonders for simplifying the setup and deployment of your site.
Why a Static Site Generator
Static sites are a great choice in any case where you have content that does not update too often. For instance, blogging, marketing sites, and even most content sites can be built and thrive as static sites generated by a tool like [Middleman][].
Since there is no application code running on the server, serving a static site to be vastly simpler than serving a dynamic site. This leads to better performance, but also has less obvious benefits like improved security. When all your server does is send existing files, security becomes almost automatic.
Similar to the security idea, using static files makes it very easy to cache the content and gain a nice performance boost (in addition to the speedup of not running application code), and all without having to think too deeply about cache invalidation. How nice!
Why Middleman
So now that we're convinced of the virtues of static sites, the question becomes "why Middleman?". The primary reason I choose Middleman is that it is very similar to Rails. As a Rails dev, it's very easy for me to map those ideas into Middleman without having to rethink or relearn. Concepts like partials, erb templating, and the asset pipeline all work largely as I'm used to, and I really appreciate that.
Initial Commit
Throughout the video we'll be working to build up a simplified version of the thoughtbot.com website. The code for this video can be found in the accompanying repository on GitHub.
Getting started with a Middleman site is very straightforward and Rails-like:
$ gem install middleman
$ middleman init my-great-project-name
From that init
command, we get an initial directory structure and
dependencies in our Gemfile. The first file of interest is the config.rb
file in the root of the generated project. This file contains all of the
configuration for our Middleman site, but currently is largely empty. We'll
come back to it in the coming steps as we add content and functionality to our
site.
Also interesting is the source/
directory. This directory contains all of
the files that will be mapped into our final build output. By default, every
file in the source/
directory will be mapped to a corresponding output file.
We can configure slightly more complex mappings from the source to the output
files, but as a start, this simplicity is really great.
Lastly, just like Rails, Middleman has a server
command that we can run to
preview our site locally, and see changes as we make them.
Cleaning up Generated Content
As a general rule with either Rails or Middleman generated sites, I like to first commit the as generated files exactly, then go through and clean them up and commit that as well. I find that it's useful to be able to see that delta and know what I removed, what I cleaned up, etc. You can see these changes in the Clean up generated files commit.
Initial Content
Now that we have a skeleton in which to build our site, we can begin populating the content. Once again, we'll be rebuilding the thoughtbot.com site, or at least a simplified form of it. You can review all of these first content changes in the Add initial content and locations page commit.
The majority of the changes in this commit served to rough out the initial
content, but the changes to the config.rb
file are worth a closer look:
configure :development do
activate :livereload
end
activate :directory_indexes
The first block activates the livereload plugin which will push content and styling (and even some JS) changes to the browser without needing to reload the page. This is nothing new, but it is nice that this functionality is so easy to opt into in a Middleman site.
The second configuration activates :directory_indexes
which updates the
mapping of source files to provide cleaner URLs. Instead of having a file like
about.html.erb
map to a URL of about.html
, it can be severed as /about
.
Much nicer!
Data Dir & Partials
Now that we have our initial content populated, we can see one of the
potential drawbacks to using a static site on the /locations
page. This page
consist of a list of all of the thoughtbot locations, with each location
rendered roughly as a card with an image, the address, etc. The simplest
approach here is just to manually copy the markup for each new location, but
this is exactly the sort of thing we've learned to avoid with Rails and
dynamic apps.
Thankfully, with Middleman we have functionality to remove this duplication and make generating pages with this sort of repeated content easy. The core feature in use here is the Middleman data directory. Each file in the data directory is a YAML (or JSON) file describing some structured data. In our case, we have a file listing out the details for each location.
# locations.yml
- city: Austin, TX
image: "austin.jpg"
address:
- 701 Brazos St.
- 5th Floor
- Austin, TX 78701
- city: Boston, MA
image: "boston.jpg"
address:
- 41 Winter St.
- 7th Floor
- Boston, MA 02108
# ... and so on
In addition, we can extract the markup for our location card into a partial, and then rendering the full list of locations becomes:
<div class="locations">
<% data.locations.each do |location| %>
<%= partial "location", locals: { location: location } %>
<% end %>
</div>
The files in this data
directory are essentially a static database. Now,
adding a new location is as easy as adding a few lines to a yaml file.
The full changes for the location data extraction can be seen in the Pull locations from data file, extract partial commit.
Assets
Now that we have our content in a solid place, we can turn our eye to the assets. We'd like to clean up the styles, and add a bit of JavaScript for some client side behavior. This is an area where Middleman really shines as it brings essentially the entirety of the asset pipeline along for us to work with.
Asset Gems
Both Bourbon and Neat, our libraries for working with Sass and semantic grids, can be added via their main gems, exactly as we would in any Rails project.
Unfortunately, a number of the JavaScript asset gems are more tightly coupled
to Rails, but via the rails-assets.org project, we can bring in just about
any JavaScript asset we need via the Gemfile
. The specific updates to the
Gemfile
are as follows:
+source "https://rails-assets.org" do
+ gem "rails-assets-jquery"
+ gem "rails-assets-moment"
+end
+
+gem "bourbon"
+gem "neat"
Styling Changes
With Bourbon
and Neat
added to the project, the first thing we'll want to
do is rename the all.css
stylesheet to all.scss
to take advantage of Sass
features like nesting, @import
and @include
, variables, and all of
Bourbon
and Neat
's niceties.
JavaScript Changes
Here again we have a very asset pipeline-like experience. We can pull in jQuery and moment.js, provided by the asset gems, and then write a simple DOM manipulation function:
//= require jquery
//= require moment
$(function() {
var today = moment().format("dddd MMM Do, YYYY");
$('footer').html('<p>Hello from jQuery land. Today is ' + today + '</p>');
});
Again, you can see all of these changes together in the Make assets fancy w/ Sass, Bourbon, Neat, jquery commit.
Deploying
At this point we have built up a great foundation with our site and it's time to get it on the web. There are many approaches we can take to this since the output of our site is just HTML, CSS, and JavaScript. Many of these approaches have Middleman specific plugins to make it easy as pie.
In our case we'll be looking at using Amazon's S3 service combined with it's static website hosting functionality to host our site cheaply and easily.
Middleman S3 Sync
In order to make this as simple as possible, we'll be using the middleman-s3_sync plugin to automate and optimize our deployment. This particular gem (there are multiple Middleman + S3 gems, yay community!) stands out to me as it strikes a great balance between convection and ease of use, while providing just about any configuration point you might need open.
Dotenv for Secrets
The one final piece needed to tie all of this together is a way to make our S3
keys accessible to the s3_sync
gem. We'll do this using the
middleman-dotenv gem. Again, very similar to a Rails app, we can put our
keys in a .env
file and have them loaded into the environment as needed.
Adding a 404 Page
S3 allows us to define an "error page" for unmatched URLs. In order to provide
this, we can opt-out of the directory_indexes
functionality for that one
file to ensure that 404.html
will indeed be in our output:
page "/404.html", directory_index: false
Deploy Script
With all of this in place, deployment is now extremely simple, but we can go one step further to wrap this up in a deploy script:
#!/bin/sh
set -e
bundle exec middleman build --clean
bundle exec middleman s3_sync
This script forces a full clean build, then pushes the files up to S3 using
the s3_sync
command.
You can check out the full changes for deployment in the Add a deployment config for S3 commit.
Performance Tweaks
Now we have our content fleshed out, assets enabled, and deployment streamlined, the one final tweak we might want to make before calling it a day is to apply a handful of performance tweaks.
The specific configurations we'll be adding are to minify our CSS and JS, gzip our files, and to apply an asset hash fingerprint. Combined, these reduce the size of every piece of content we serve, and in the case of the fingerprinting, they allow for optimized long lived caching.
In order to activate the performance tweaks, we only need to modify the
config.rb
file (a very good sign), adding the following:
+configure :build do
+ activate :gzip
+ activate :minify_css
+ activate :minify_javascript
+ activate :asset_hash
+end
+
activate :dotenv
-activate :s3_sync
+activate :s3_sync do |s3|
+ s3.prefer_gzip = true
+end
Once again, this is an example of Middleman making it very easy to enable the same sort of workflows and optimizations we're used to from Rails. You can check out the full performance changes in the Gzip and fingerprint assets commit.
Additional Resources
And with that, we have our site built out. If you're interested in diving deeper, the following resources will keep you on the Middleman train.
This is a companion discussion topic for the original entry at https://upcase.com/videos/building-static-sites-with-middleman