View Source README

Build Status


A static site generator, build for speed and flexibility.

Project Creation

Install the project generator:

mix archive.install hex fermo_new

Generate the project:

mix PATH

Install dependencies:

mix deps.get
yarn install

Build the project:


Live Dev Mode

Have pages reloaded when structure, style or content change.


The live site is available at http://localhost:4001/

Page dependencies are monitored and are reloaded in the browser when changes are detected.


Project Structure

+-- build             - The built site
+-- lib
|   +-- my_project.ex - See [Configuration](#configuration)
|   +-- helpers.ex
+-- mix.exs           - See [Mix configuration](#mix-configuration)
+-- package.json
+-- priv
|   +-- locales       - See [Localization](#localization)
|   |   +-- en.yml
|   |   +-- ...
|   +-- source
|       +-- javascripts
|       +-- layouts
|       +-- localizable
|       +-- templates
|       +-- partials
|       +-- static
|       +-- stylesheets
|       +-- templates
+-- webpack.config.js

Mix Configuration

defmodule MyProject.MixProject do
  use Mix.Project

  def project do
      compilers: Mix.compilers() ++ [:fermo],
      deps: deps()

  defp deps do
      {:fermo, "~> 0.16.4"}


Create a module (under lib) with a name matching your MixProject module defined in [mix.exs](#mix-configuration).

This module must implement config/0, a function that returns an updated [config](#config-object).

defmodule MyProject do
  @moduledoc """
  Documentation for MyProject.

  use Fermo

  def config do
    config = initial_config()

    {:ok, config}

Fermo Invocation

The command

use Fermo

prepares the initial config structure.


Simple Excludes

In order to not have your template files automatically built as simple files use :exclude.

  use Fermo, %{
    exclude: ["templates/*", "layouts/*", "javascripts/*", "stylesheets/*"],

Config-defined Pages

Most static site generators build one webpage for every source page (e.g. Hugo).

Middleman provides the very powerful but strangely named proxy, which allows you to produce many pages from one template. So, if you have a local JSON of YAML file, or even better an online CMS, as a source, you can build a page for each of your items without having to commit the to your Git repo.

In Fermo, dynamic, data-based pages are created with the method in your project configuration's build/0 method.

  def build do
    foo = ... # loaded from some external source
      %{foo: foo, locale: :en}


Out-of-the-box, Fermo supports EEx and SLIM templates

  • simple templates - any templates found under priv/source will be built. The partials directory is excluded by default - see excludes.
  • page templates - used with config-defined pages,
  • partials - used from other templates,
  • localized - build for each configured locale. See localization



At the beginning of any template, you can place 'frontmatter', a block of YAML, which supplies the default values related to the template.

Frontmatter can be used to set the layout:

layout: "foo"

or to skip the layout:

layout: null



Top level pages are called with the following parameters:

  • params - the parameters passed directly to the template or partial,
  • context - hash of contextual information.



  • :env - the application environment,
  • :module - the module of the compiled template,
  • :template - the top-level page or partial template pathname, with path relative to the source root,
  • :page - see below.



Information about the top-level page.

  • :template - the template path and name relative to the source root,
  • :filename - the path of the generated file relative to the build directory. Note that this filename gets standardized. E.g., if you supply "foo.html", that will get corrected to "foo/index.html",
  • :path - the online path of the page,
  • :params - the parameters passed to the template.



Partials are also called with the same 2 parameters, but the values in :page are those of the top-level page, not the partial itself.

Associated Libraries


If you pass an :i18n key with a list of locales to Fermo, your locale files will be loaded at build time and files under localizable will be built for each locale.

defmodule MyProject do
  @moduledoc """
  Documentation for MyProject.

  use Fermo, %{
    i18n: [:en, :fr]




Fermo can optionally create a mapping of translated paths for any page.

This allows you to easily manage language switching UIs and alternate language meta tags.

To activate localized_paths, you need to pass a flag in your initial config:

defmodule MyProject do
  use Fermo, %{
    i18n: [:en, :fr],
    path_map: true,


Then ensure you pass an :id and :locale in the params of your calls:
  %{post: post, locale: :fr, id: "post-#{}"}

When you do this, Fermo will collect together all pages with the same :id so when your template is called, it will have a :localized_paths Map available:

  localized_paths: %{
    en: "/posts/about-localization",
    fr: "/posts/a-propos-de-la-localisation",

You can then use :localized_paths to build create links between the different language versions of a page.

You can do the same for non-dynamic localized pages too, by indicating the id in the template's frontmatter:

id: my-localized-page


<a name="webpack-asset-pipeline"></a>Webpack asset pipeline

mix runs Webpack to produce static assets.

mix runs Webpack dev server.

It is assumed that you are using Webpack >= 5.

If you are using Webpack <= 4.x, you'll need to add the following to your config/config.exs:

config :fermo, webpack_dev_server_command: "yarn run webpack serve --watch-stdin"


There is a very slow (40s) integration test that builds a project - the time is mostly taken up compiling dependencies.

By default integration tests are skipped when you run

$ mix test

To run all tests, add the FERMO_RUN_INTEGRATION environment variable:



$ mix coverage

HTML coverage:

$ mix coveralls.html

Middleman to Fermo

Fermo was created as an improvement on Middleman, so its defaults tend to be the same its progenitor.

See here.