Following on from our last post on using Middleman as a blog platform, we wanted to take a deeper dive into how to go from start to finish deploying a Middleman blog to Heroku.
Launching this blog was our first time working with Middleman, which so far couldn't have been easier, and combined with Heroku's recent GitHub integration, we now have a particularly nice workflow in place; to publish a new post, the author simply needs to create a new markdown file with the article content, commit this, and push to Github. We have two primary branches - a dev branch which automatically deploys to a staging version of the blog, and the master branch, that deploys the production version. While Middleman provides a great local server for previewing your site live as you're writing, one of the really nice side-benefits of this workflow is that we don't have to go through a desktop environment to publish - in effect, Github has become our web-based CMS to our static blog!
There is a lot of really great flexibility that Middleman provides, and while this article will dip into a few of those areas, the main intent is to highlight the simplest path to get up and running on Heroku, including a few features that are not essential for Heroku, but we found very useful when developing this blog.
This is meant to be complimentary to Middleman's own docs, so if something is only lightly touched on here, check out the more in-depth background provided on the Middleman site.
This post is split into three parts:
Part 1: Setup a new middleman-blog app
Finally before we begin, I have created a repo at https://github.com/pixelcabin/middlemanblog_heroku_template showing the end result of the steps outlined below, which is running on Heroku at https://blog-template-staging.herokuapp.com - see the footnote for each section to view the corresponding diff.
Part 1 - Setup a new Middleman-blog app1
There is a lot of info on setting up the middleman-blog
extension in the official documentation, however if you're already familiar with that, you can kick off with:
gem install middleman
gem install middleman-blog
middleman init MY_BLOG_PROJECT --template=blog
Middleman uses a config.rb
file in your project root to set up various options. We will be coming here a lot, but to start with, let's set up the location and file format of our articles:
1 2 3 4 5 6 | activate :blog do |blog| #… blog.sources = "articles/{year}-{month}-{day}-{title}" blog.default_extension = ".md" #… end |
Once setting this, you'll want to move the example article into source/articles
, and rename to a .md
extension (making sure to drop the .html
part of the filename too).
Now, start up the local development server with bundle exec middleman server
. You should now have a basic middleman-blog app, with the default example article visible.
Create an article layout
By default, Middleman will render the contents of your markdown article into the <%= yield %>
tag of source/layout.erb
, however if you want your article to look different to the rest of your blog, you will probably want to set up a separate layout for it.
The simplest way to do this is to create a new article_layout.html.erb
file, and include the following:
Then, within config.rb
:
This uses nested layouts to nest the article's layout within the main layout, and then the contents of your markdown file will be rendered where yield
is called within article_layout
. You can optionally move these into a layouts
subfolder to keep them organized, and Middleman will still find them.
A quick Middleman cheat sheet
More in depth info can be found under Listing Articles, along with the RDoc for the gem (BlogArticle is of particular interest).
Index
page_articles
- Assuming pagination is enabled, the collection of articles for the current page (otherwiseblog.articles
will return all of the blog's articles)article.data
- the article's frontmatter as a structured object - i.e. if the frontmatter includesfeatured
, this can be accessed asarticle.data.featured
article.url
the relative path to the article
Article layout
current_article
- the current article
Custom Helpers
Middleman supports writing custom helper methods - these are described under Custom Defined Helpers, and can really help with keeping your templates dry.
Part 2 - Production environment config2
Setup RSS Feed
Middleman blog will generate a template source/feed.xml.builder
, however this needs updating before you launch your blog.
site_url
This should be updated to whatever domain your blog will be hosted on, however for a deployment to Heroku, we set this up as a config variable APP_DOMAIN
, which allows this to be set on an app-by-app basis without needing to modify the codebase. For info on setting these on Heroku, see Heroku's docs on Configuration and Config Vars.
1 | site_url = "https://#{ENV['APP_DOMAIN']}/"
|
Once this is updated, modify any other relevant properties. This will then expose an rss feed available at /feed.xml
Configure sitemap
There is a great Middleman plugin that will do the work for you called search_engine_sitemap. Once configured, this will output the compiled sitemap at /sitemap.xml
- Add
gem 'middleman-search_engine_sitemap'
to your Gemfile - Add the following to
config.rb
, before theconfigure :build
block
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | set :url_root, "https://#{ENV['APP_DOMAIN'] ? ENV['APP_DOMAIN'] : 'localhost:4567'}" activate :search_engine_sitemap, exclude_if: -> (resource) { # Exclude all paths from sitemap that are sub-date indexes resource.path.match(/[0-9]{4}(\/[0-9]{2})*.html/) }, default_change_frequency: 'weekly' # Filewatcher ignore list (workaround for search_engine_sitemap on # Heroku - see https://github.com/Aupajo/middleman-search_engine_sitemap/issues/2) set :file_watcher_ignore, [ /^bin(\/|$)/, /^\.bundle(\/|$)/, # /^vendor(\/|$)/, /^node_modules(\/|$)/, /^\.sass-cache(\/|$)/, /^\.cache(\/|$)/, /^\.git(\/|$)/, /^\.gitignore$/, /\.DS_Store/, /^\.rbenv-.*$/, /^Gemfile$/, /^Gemfile\.lock$/, /~$/, /(^|\/)\.?#/, /^tmp\// ] |
Compress assets
Add gem 'middleman-minify-html'
to your gemfile, then modify the configure :build
block in config.rb
as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # Build-specific configuration configure :build do # For example, change the Compass output style for deployment activate :minify_css # Minify Javascript on build activate :minify_javascript # Enable cache buster activate :asset_hash activate :gzip # Use relative URLs # activate :relative_assets activate :minify_html do |html| html.remove_http_protocol = false end # Or use a different image path # set :http_prefix, "/Content/images/" end |
JS Compilation
Add the following gems to speed up JS compilation (optional):
Part 3 - Prepping for Heroku3
There are a few things that need to be set up for use on Heroku:
- Gemfile - add additional gems required for various rack plugins
- Rakefile - provides the relevant code to allow Heroku to run
middleman build
when compiling a new build - config.ru - the rackup file that will be used by heroku to start up the Rack server
- 404 Page
- Procfile - used by Heroku to start up the web process
Gemfile
Add the following gems:
Rakefile
Create a new file Rakefile
, and paste in the following:
1 2 3 4 5 6 7 | require 'bundler/setup' namespace :assets do task :precompile do sh 'middleman build' end end |
config.ru
Create a new file config.ru
, and paste in the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | #\ -s puma require 'rack' require 'rack/contrib/try_static' require 'rack/deflater' require 'rack/cache' # Forces SSL on all requests unless ENV['RACK_ENV'] == 'development' require 'rack/ssl' use Rack::SSL end use Rack::Cache, :verbose => true, :metastore => 'file:/var/cache/rack/meta', :entitystore => 'file:/var/cache/rack/body' # Enables compression of http responses, used in conjunction with `activate :gzip` in config.rb use Rack::Deflater ONE_WEEK = 604_800 # Serve files from the build directory use Rack::TryStatic, root: 'build', urls: %w[/], try: %w(.html index.html /index.html), header_rules: [ [ %w(css js png jpg woff html), { 'Cache-Control' => "public, max-age=#{ONE_WEEK}" } ] ] run lambda { |env| four_oh_four_page = File.expand_path('../build/404/index.html', __FILE__) [ 404, {'Content-Type' => 'text/html', 'Cache-Control' => "public, max-age=#{ONE_WEEK}"}, [ File.read(four_oh_four_page) ] ] } |
404 Page
Create a new file source/404.html.erb
, and add content as appropriate - lines 27-30 of config.ru
will handle returning the 404 page if Rack::TryStatic
does not find any suitable files to return.
Procfile
Create a new file Procfile
, and paste in the following:
web: bundle exec puma -p $PORT
Once this is all set up, create your Heroku app, set the APP_DOMAIN
config var to the domain you'll be accessing the app on, and push to Heroku.
Building this blog has been my first time working with Middleman, so if you've spotted something that could be improved, let me know in the comments below, or on Twitter at @michaelrshannon!
Edits
While prepping our next article, we noticed that links were having their protocol removed on build, meaning that if the site was loaded over https (as ours is), all links would end up becoming https too, which could cause problems with external resources.
To fix this, the section Compress Assets has been updated to instruct the minify_html
Middleman extension not to remove the http protocol on compression, which it otherwise does by default.