Skip to content

Instantly share code, notes, and snippets.

@mrrooijen
Created February 1, 2012 18:04
Show Gist options
  • Star 34 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save mrrooijen/1718367 to your computer and use it in GitHub Desktop.
Save mrrooijen/1718367 to your computer and use it in GitHub Desktop.
MiddleMan on Heroku configuration.
.DS_Store
*.swp
*.swo
Gemfile.lock

Instructions

If you don't already have a MiddleMan website, simply generate a new one by running:

middleman init my_new_website --bundler --rack

When ready, copy over the contents of the files in this gist/repository to your own project.


To run in development mode:

bundle exec thin start -p 3000

To have MiddleMan auto-build on each save, we use the Watchr gem. It will auto-build on each save in the source directory:

bundle exec watchr ./Watchrfile

To deploy to Heroku:

heroku create mywebsite --stack cedar
git push heroku master

Assuming you've committed everything you were going to deploy.

Also, note that you must pass in the --stack cedar for various reasons, such as:

  • Being able to write to the filesystem (anywhere, not just ./tmp)
  • Being able to use Procfile to tell Heroku to render/compile the site to build (aka public) before running the Thin server

How does it work?

When the website has been deployed to Heroku, it will, on the first request, compile the website to the build directory. Once compiled, it will spin up a Thin server to serve the pages. Now since the only thing it serves will be static pages, it will still be blazing fast, regardless of whether it has to go through the Rack-layer. The reason this going behind a Rack layer is because we're using Rack::TryStatic from Rack-Contrib. This will allow us to have "pretty" URLs. So for example say you have a request come in at http://mywebsite.herokuapp.com/some-page it will look for a file in build/some-page.html.

There is no longer the need to pre-compile your static files locally, commit them and then push them to Heroku. Just push your dynamic code to Heroku and it should just work.

# encoding: utf-8
require File.expand_path("../rack_try_static", __FILE__)
use ::Rack::TryStatic,
:root => "build",
:urls => ["/"],
:try => [".html", "index.html", "/index.html"]
run lambda { [404, {"Content-Type" => "text/plain"}, ["File not found!"]] }
source "http://rubygems.org"
gem "thin"
gem "middleman", "3.0.0.beta.1"
group :development do
gem "heroku"
gem "watchr"
gem "rb-fsevent"
gem "growl_notify"
end
web: bundle exec middleman build && thin start -p $PORT
# encoding: utf-8
module Rack
class TryStatic
def initialize(app, options)
@app = app
@try = ["", *options.delete(:try)]
@static = ::Rack::Static.new(
lambda { [404, {}, []] },
options)
end
def call(env)
orig_path = env["PATH_INFO"]
found = nil
@try.each do |path|
resp = @static.call(env.merge!({"PATH_INFO" => orig_path + path}))
break if 404 != resp[0] && found = resp
end
found or @app.call
end
end
end
# encoding: utf-8
watch("source") { system("bundle exec middleman build") }
@JeanMertz
Copy link

Hey @meskyanichi, I got here from your recommendation at winton/stasis#33.

I love this setup, and I converted my app, which works great with Heroku. However, it seems none of the assets (or html) gets cached, which causes the site to be pretty slow because of the use of some large images. Is there any way to solve this using Rack or anything else?

You can see what I mean by visiting this site: http://staging.immens-maastricht.nl/, you can see the large images being downloaded every time you refresh the page.

@mrrooijen
Copy link
Author

Hi @JeanMertz

I'm not entirely sure, haven't looked in to this. However, you might be able to get some caching going using Rack::Cache. It has some resources there that might point you in the right direction. If you figure out a way to improve performance using this, or any other method, please let me know.

@mrrooijen
Copy link
Author

Note: I just found Rack::StaticCache this might be even better as it states the following:

The Rack::StaticCache middleware automatically adds, removes and modifies
stuffs in response headers to facilitiate client and proxy caching for static files
that minimizes http requests and improves overall load times for second time visitors.

Might be worth a look at as well. I think with MiddleMan it would work like so:

use Rack::StaticCache, :urls => ["/images", "/stylesheets", "/javascripts"], :root => "build"

Or maybe it can cache absolutely everything with:

use Rack::StaticCache, :urls => ["/"], :root => "build"

You'd have to experiment with it and see if it works/helps.

@JeanMertz
Copy link

@meskyanichi hmm not sure, I followed @tdreyno his example but it doesn't seem to cache anything. I still see GET 200 OK requests and no improvement on loading times.

Also the heroku logs show the 200 status codes:

2012-02-09T19:05:01+00:00 heroku[router]: GET staging.immens-maastricht.nl/images/eindejaarsevenement-2011/banner1.jpg dyno=web.1 queue=0 wait=0ms service=19ms status=200 bytes=154154
2012-02-09T19:05:01+00:00 heroku[router]: GET staging.immens-maastricht.nl/images/eindejaarsevenement-2011/banner4.jpg dyno=web.1 queue=0 wait=0ms service=15ms status=200 bytes=129786

Here's my config.ru:

# encoding: utf-8
require 'rack'
require File.expand_path("../rack_try_static", __FILE__)

use ::Rack::TryStatic,
  :root => "build",
  :urls => ["/"],
  :try  => [".html", "index.html", "/index.html"]

require "rack/contrib/static_cache"
use Rack::StaticCache, urls: ['/'], root: 'build'

run lambda { [404, {"Content-Type" => "text/plain"}, ["File not found!"]] }

edit here are two more sources, both use the same technique, so it must be something on my end... weird

https://github.com/blt/utf8ouch/blob/master/config.ru
http://www.syamantics.com/rack-static-cache-middleware-for-caching-static-contents

@JeanMertz
Copy link

Well, I thought I found something here: http://www.mail-archive.com/heroku@googlegroups.com/msg05335.html where they talk about Heroku overriding cache-control using Heroku::StaticAssetsMiddleware

But I don't think that's the case anymore for the Cedar stack.

Here's the new config.ru that I tried, same result, 200 OK instead of 304 Not Modified (304's do show up locally during testing, but not any more on Heroku)

# encoding: utf-8
require 'rack'
require 'rack/contrib/static_cache'
require 'rack/contrib/try_static'
#require File.expand_path('../rack_try_static', __FILE__)

module Heroku
  class StaticAssetsMiddleware
    def initialize(app)
      @app = app
    end

    def call(env)
      @app.call(env)
    end
  end
end

use Rack::TryStatic,
  :root => 'build',
  :urls => ['/'],
  :try  => ['.html', 'index.html', '/index.html']

use Rack::StaticCache, urls: ['/'], root: 'build'

run lambda { [404, {'Content-Type' => 'text/plain'}, ['Pagina niet gevonden. Bezoek www.immens-maastricht.nl en probeer het opnieuw.']] }

@SachaG
Copy link

SachaG commented Apr 9, 2013

Any solution to that caching problem? My site is running fine on Heroku, except like JeanMertz said none of the images are getting cached…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment