Middleman Tutorial (v4): Enable Static E-Commerce on a Ruby Site Generator

In a rush? Skip to tutorial steps, or GitHub repo + live demo.

"Static" websites have skyrocketed in the last years. They're fast, scalable, secure and cheap. For years, dynamic sites had the upper hand regarding the functionality argument. Today, an abundance of dedicated APIs can be added to your frontend projects, and developers can do anything they want with static websites. We call this the JAMstack! Coupled with new content management tools (static/headless CMS), this means serious benefits for clients.

From .NET toolkits to React-powered tools, there are LOTS of site generators available.

Today, we'll focus on handling e-commerce on a static site with a Middleman tutorial.

Middleman app, a powerful Ruby site generator

snipcart-middleman-tutorial-static-site-ecommerce

Middleman is a Ruby site generator offering a modern frontend framework used by the likes of MailChimp & Thoughtbot.

We first heard about it when we sat down with Dillon Morton, a Colorado web developer. He had used the app to enable e-commerce on his clothing brand's static site and seemed happy about it. We had been eager to try it ourselves for a while!

Under the hood: A word on Ruby and Ruby e-commerce

ruby-ecommerce-stack

We're hardwired geeks at Snipcart, always curious as to how the tools we experiment with are built. In this case, what's used to power Middleman is the now-famous Ruby. Created by Yukihiro Matsumoto in the mid-90's, Ruby is a dynamic, general-purpose programming language. Its popularity surged after a smart fella built a world-class tool on top of it in 2003: Ruby on Rails. In fact, some keep confusing the MVC framework, Rails, with its original language.

Even stripped from its household web framework, Ruby remains a very cool development tool. It's both fun and fast to write, effectively decreasing your project's time-to-market. A little slower than languages like Go, it also promotes a strong open source philosophy and is surrounded by a solid, supportive community. In a sense, it shares many pros & cons with Python, which we touched on previously.

If you're looking at building a Ruby online shop, you'll likely stumble upon Ruby on Rails e-commerce solutions first. Apps like Shoppe, Spree Commerce, and Solidus can be great starting points. But in certain cases, their hosting & backend requirements might be overkill.

In this post, we're proposing a different approach. A modular, JAMstack approach. We're going to leverage Ruby through Middleman, generating a static site, and then use our HTML/JS shopping cart to enable e-commerce.

Snipcart & Middleman tutorial: getting started

Pre-requisites: I'm assuming you have Ruby installed, RubyGems working, and minimal knowledge of Middleman. See how to install Ruby on Mac here and PC here. Find useful information regarding RubyGems here. Learn Middleman basics & advanced features in their documentation. You'll also need a free Snipcart account in Test mode.*


First, install Middleman:

gem install middleman

In your project folder, you will need to init a new Middleman app using their CLI:

middleman init

Middleman will create the basic project structure: a source folder and some other files. The source contains templates, stylesheets, and any other assets.

To start a development server, just do:

middleman server

Your website will then run on a local server. Simply hit the local URL you see in the terminal.

1. Adding Snipcart products to Middleman

For this demo, we'll use a simple yml file as our datastore. Middleman works great with this, and it'll be easy to deploy/update.

At the root of your project, create a data folder named products.yml.

We can now add products with Snipcart's required properties:

- name: Chocolate Chip Cookies
  id: 101
  price: 10.99
  max_quantity: 10
  path: products/chocolate-chip-cookies
  image: https://snipcart.com/media/10107/chocolate-chips-cookies.png
  description: Uncontested, delightful classic.

- name: Oatmeal Raisin Cookies
  id: 102
  price: 8.00
  max_quantity: 20
  path: products/oatmeal-raisin-cookies
  image: https://snipcart.com/media/10108/oatmeal-raisin-cookies.png
  description: Delicious, all-day treat.

- name: Peanut Butter Cookies
  id: 103
  price: 10.00
  path: products/peanut-butter-cookies
  image: https://snipcart.com/media/10109/peanut-butter-cookies.png
  description: Savoury, timeless pick.

This data will be available in your Middleman templates using data.products.

2. Setting up Middleman layout page with Bulma

For styling this demo, I decided to use Bulma. It's a kickass CSS framework based on Flexbox, which makes it easy to build good-looking sites. We'll reference Bulma using their CDN server.

In our layout.erb file, we'll add the following lines to include Bulma and jQuery:

<!-- Include jQuery -->
<%= javascript_include_tag "//cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js" %>

<!-- Include Bulma -->
<%= stylesheet_link_tag "//cdnjs.cloudflare.com/ajax/libs/bulma/0.3.1/css/bulma.min.css" %>

Let's first put a simple nav bar. Add these lines to the body tag just before <%= yield %>:

<nav class="nav">
  <div class="nav-left">
    <a class="nav-item subtitle is-3" href="/">
      <strong>Cookies</strong> &nbsp;store
    </a>
  </div>
  <div class="nav-right nav-menu snipcart-summary">
    <span class="nav-item">
      <a class="button is-white snipcart-checkout" href="#">
        <span class="icon is-small">
          <i class="fa fa-shopping-cart" aria-hidden="true"></i>
        </span>
        <span>
          View cart (<span class="snipcart-total-items">0</span>)
        </span>
      </a>
    </span>
  </div>

We'll then add a footer in the layout page, just before closing the <body> tag:

<footer class="footer">
  <div class="container">
    <div class="content has-text-centered">
      <p>
        <strong>Cookies Store</strong> by <a href="https://snipcart.com">Snipcart</a> folks. This is a demo of a static site generated by Middleman. 
        Thanks to <a href="http://bulma.io">Bulma</a> for the simple CSS framework.
      </p>
      <p>
        <a class="icon" href="https://github.com/snipcart/snipcart-middleman-integration">
          <i class="fa fa-github"></i>
        </a>
      </p>
    </div>
  </div>
</footer>

And now let's add a cart summary in the nav section to let customers access their cart from any page. This link will contain the current number of items in the cart. To do so, include Font Awesome to have access to some icons. Here we'll just add their asset through a CDN in the layout file.

<!-- Include Font Awesome -->
<%= stylesheet_link_tag "//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" %>

Of course, we also have to include Snipcart's required files. Log in to your Snipcart dashboard, go to Account Menu > API Keys and copy the code snippet. We'll add it to the layout just after Bulma and jQuery.

We could just copy/paste it, but let's stay consistent and use Middleman helpers:

<!-- Include Snipcart -->
<%= javascript_include_tag  "https://cdn.snipcart.com/scripts/2.0/snipcart.js",
    :id => "snipcart",
    :"data-api-key" => ENV["snipcart-api-key"] %>
<%= stylesheet_link_tag "https://cdn.snipcart.com/themes/2.0/base/snipcart.css" %>

We'll need to define an environment variable somewhere with our Snipcart API Key. Since I'll use Netlify for deployment, I'll add my variable in their dashboard, but you could just add it to your config.rb file.

ENV["snipcart-api-key"] = "YOUR_API_KEY"

We'll also need another environment variable to keep the base site URL. I set it in Netlify as well, but while developing I added this:

ENV["base-url"] = "http://localhost:4567"

This will make sure Snipcart's crawlback validation works as expected.

3. Setting up your static site's index

On the main page, we'll display all our products. Here, we can completely remove the content of the index.html.erb file in Middleman.

Start by adding the page title using yml markup:

---
title: My awesome store
---

Then, add some code to make a nice header on the site:

<div class="hero-body">
  <div class="container">
    <h1 class="title">
      Snipcart's awesome cookie store
    </h1>
    <h2 class="subtitle">
      Buy delicious cookies from our store, you won't really receive them, but you'll be able to try our product; Snipcart ;)
    </h2>
  </div>

We now want to display all products by getting them from the products.yml, and Middleman makes it very easy.

You can loop through your products using the data object.

<% data.products.each do |p| %>
    <%= p.name %>
<% end %>

Let's use the Bulma Card component combined to their grid system to show products.

<section class="section">
  <div class="columns">
    <% data.products.each_with_index do |b, index| %>
      <div class="column">
        <div class="card">
          <div class="card-image has-text-centered">
            <a href="<%= b.path %>">
              <img src="<%= b.image %>" style="max-width: 90%; min-height: 200px; width: auto; height: auto" />
            </a>
          </div>
          <div class="card-content">
            <div class="content">
              <p class="title is-4"><%= b.name %></p>
              <p class="subtitle is-6"><%= number_to_currency(b.price, :unit => "$") %></p>
            </div>
          </div>
          <footer class="card-footer">
            <span class="card-footer-item">
              <%= snipcart_button b, "Add to cart" %>
            </span>
          </footer>
        </div>
      </div>
    <% end %>
  </div>
</section>

Please note the snipcart_button helper. We'll get back to that later, but it's basically going to be a helper to render a buy button with Snipcart's required attributes.

4. Creating a product details page for your store

First step here is to create a new erb template. Let's call it product.html.erb. Put it at the root of the source folder.

This template will display the product details and the Snipcart buy button.

For this tutorial, we'll come up with a simple two columns template with the product image, Snipcart button, product name, and its description.

---
layout: layout
---

<div class="container">
  <div class="columns">
    <div class="column is-third">
      <%= image_tag product.image %>
    </div>
    <div class="column">
      <h1 class="title">
        <%= product.name %>
      </h1>

      <h2 class="subtitle">
        <%= number_to_currency(product.price, :unit => "$") %>
      </h2>

      <div class="content">
        <p>
          <%= product.description %>
        </p>
        <p>
          <%= snipcart_button product, "Add to cart" %>
        </p>
      </div>
    </div>
  </div>
</div>

Notice the snipcart_button product line here? Unfortunately, it won't magically work; we have a little extra step to do: creating a template helper to generate a Snipcart button.

Add this to your config.rb:

helpers do
  def snipcart_button (p, text)
    args = {
      :"class" => "snipcart-add-item button is-primary is-medium",
      :"data-item-id" => p.id,
      :"data-item-price" => p.price,
      :"data-item-name" => p.name,
      :"data-item-max-quantity" => p.max_quantity,
      :"data-item-url" => ENV["base-url"] + p.path,
      :"data-item-image" => p.image
    }

    content_tag :button, args do
      text
    end
  end
end

With this, our add to cart button will show with product info when we call snipcart_button inside a template.

Now that we have a template and a helper to generate the Snipcart button, we need to be able to display it when the product URL is called. To do so, we have to use a Middleman proxy. To learn more about this, read this docs section.

Add this to your config.rb:

data.products.each do |p|
  proxy p.path + "/index.html", "product.html", :locals => { :product => p }, :ignore => true
end

When defining products in products.yml, we specified a path. This path will be used as the unique URL to access the resource.

Now, open your browser (make sure your Middleman server is started) and hit a product page such as http://localhost:4567/products/chocolate-chip-cookies/. You should now see the product and be able to add it to your cart!

snipcart-middleman-tutorial-local-site-product

snipcart-middleman-tutorial-local-site-cart

5. Deploying your static e-commerce site using Netlify

Netlify's an awesome product developed by talented developers we met a while ago. It allows developers to focus on the code while it builds, deploys and hosts their static sites. We'll use it to deploy our Middleman demo.

First thing I suggest you do is create a file named .ruby-version at the root of your project. Netlify will use the version of Ruby specified there to build your site. As we are using the version 4.2.0 of Middleman, we must use a recent version of Ruby. I decided to use the 2.3.1 version. The created file will only contain this information.

In your Netlify dashboard, click on Add a new project and follow the steps to link your GitHub repository.

After that, Netlify will automatically detect that it's a Middleman project and suggest a config. Just hit Build your site.

snipcart-middleman-ruby-netlify-config

My site's now deployed and blazing fast!

GitHub repo & live e-commerce demo

middleman-static-site-ecommerce-demo Peep my "not-so-bad for a former backend guy" design. Thanks again, Bulma.

See the live demo

Check out the full GitHub repo

Conclusion

Tools like Middleman and Netlify make building and deploying static websites more efficient than ever. Except getting Ruby to work A1 on my Windows machine, everything went smoothly while crafting this tutorial with Middleman. It took me about 4 hours, including deployment and learning Bulma basics. With more time, I could've put together stuff like a bigger inventory, multiple images per product, and advanced cart customization.

We hope this post gave you a good idea of how you can handle e-commerce or dynamic features on static sites!

Check out all of our JAMstack tutorials here.

We publish technical and editorial posts like this one on a regular basis, so feel free to subscribe to our newsletter to stay in the loop!


Have you used any of the tools mentioned in the post? If so, we'd love to hear your thoughts in the comments. And if you found the article valuable, feel free to share it on Twitter!

Suggested posts: