SW engineering, engineering management and the business of software

subscribe for more
stuff like this:

Static Site Generator using Elixir and Phoenix

Phoenix is a great web framework. It can do more traditional MVC server-side web app work or get fancy with ultra-responsive web apps via sockets and LiveView.

One thing that Phoenix normally doesn’t do is static site generation.

I say normally because I love static site generation for things like marketing websites or launch pages or blogs. With blogs in particular, being able to write in markdown in any text editor is a kind of super power. It reduces friction significantly enough to materially change the volume of words & post output for all two of my lovely readers out there. And of course, they are dead simple to deploy and host.

So now that I’m big into Elixir, and I had a marketing blog on a different site that was a bit fragile and crufty, I figured there must be a way to:

  1. Use Phoenix
  2. Write in markdown
  3. Generate a static site

Turns out there is.

Phoenix and Markdown

There are two important libraries for doing markdown in a Phoenix app:

Earmark is a simple markdown processor. You give it markdown and it spits out HTML.

If you have some markdown stored in a variable, say named @markdown_blurb, you could do the following:

<%= raw(Earmark.as_html!(@markdown_blurb)) %>

phoenix_markdown uses earmark internally. It is a Phoenix template engine implementation, so with a bit of config, you can simply render a markdown file like you would any html.eex template file. For example, given some_markdown.html.md, you could render it like so:

<% render("some_markdown.html") %>

Given these two pieces you have some great options for writing markdown and publishing it to the web.

Hat Tip to Tjaco Oostdijk for his great article on Phoenix, Markdown and template engines.

Site Generation by using ConnCase

As stated above, I adore static site generation. Phoenix doesn’t really have any native capability to spit out static copies of a given route.

Just rendering templates doesn’t work, because you lose anything that might get assigned in the controllers.

There is one place however, that allows you to process a route as if a browser hit it and inspect the results: The unit test framework.

In a nutshell, to get a static site copy, all I did was create a unit test file called exporter_test.exs and then give it a list of routes. You can get the response of a given route with simple unit test macros like so:

conn = Phoenix.ConnTest.build_conn()
conn = get(conn, route_path)
resp = html_response(conn, 200)

Then it’s just writing the resp variable to a file.

I ended up writing the file directly into the priv/static directory. That directory already has all the css, js, images and whatnot already.

It’s a simple matter of using rsync to push it up to a static host after that.

The entire method to write a single route to priv/static looks like this:

Further improvements.

So currently, to deploy all I do is mix test and then run a simple deploy script that rsync’s priv/static to the www root of the cloud instance.

There are lots of places for improvement. I probably should create a custom mix task. If you delete a markdown file, that file won’t be deleted in the priv directory.

But for a quick and dirty static site generator, this trick works surprisingly well.

in lieu of comments, you should follow me on twitter at twitter/amattn and on twitch.tv at twitch.tv/amattn. I'm happy to chat about content here anytime.

the fine print:
aboutarchivemastodontwittertwitchconsulting or speaking inquiries
© matt nunogawa 2010 - 2023 / all rights reserved