Recently, I have the possibility to develop a website from the ground up with Elm: it was born as an experiment to showcase the Elm (at version 0.18) language at MaltaJS presentation and it became the community’s official website!

Although, I found myself coping with an annoying issue when we were posting our page on Facebook and Twitter. The link looked like this:

An ugly link

An ugly link

Clearly, I wanted my link to look like the cool links with an image and a description.

After a bit of investigation, it turned out that I was missing a couple of settings and a critical feature: namely the Open Graph tags and a static page.

The static page

When a web crawler hits our website, it fetches only the static content. It means that in my case it was looking at this index.html (click here for the full version):

The only HTML seen by the crawlers was a poor div named main.

Of course, it didn’t work as expected!

But the HTML is generated at runtime when using Elm. How can I get it before?

A static renderer

Thanks to the amazing Elm community, I got a solution for the pre-rendering: the elm-static-html package by Noah, a well know Elm contributor. This package allows us to take a snapshot of our application based on the view function.

It works from the command line taking our main Elm file as input and looking up for a view function with this signature:

Good. But my view has a different signature since it accepts a model as an input:

The solution is already here, indeed I already have to provide an initialModel for my application thus I jus need to define a curried function for the static renderer:

Note that this way I am free to serve the application in a different state, rather than the initial one.

All I need to do now is to configure the renderer using a configuration file, in order to let it know which function to use and where to put the result:

This way I can run it just passing the configuration, like elm-static-html -c elm-static-html.json.

And this solves part of the problem. Now that I can produce an initial snapshot of my page, I need to deliver it to the right clients: for sure a human won’t be interested in the static page, but rather in the whole application!

Detecting the web crawlers

In order to discriminate between normal clients and web crawlers, I used a very nice npm package named spider-detector which works this way:

If hits a spider (or crawler), the static page will be sent, otherwise the normal page with the JS bundle.

Putting all together

So far we have collected:

  1. a utility which takes a screenshot of our Elm application at a certain state
  2. a pre-rendered static page
  3. a way to serve different content to web crawlers

What we need it is to have the static page rebuilt each time we modify the source code and the possibility to reuse the same HTML, so that we don’t repeat ourselves.

The problem with the HTML is that, in my case, the <head> part of the page is the same both for the static and the dynamic one. What changes is the part inside the <body> and <div id="main"> which in the former case contains HTML only and in the latter contains the javascript code to run.

Splitting the HTML

I tried using a template language like Pug (formerly Jade), but it was taking too much time and I was on a tight schedule, so I decided to split the basic components of the page into three different files and assemble them at build-time simply using gulp-concat.

Here’s the division I have used:

  • the file intro.html contains the common initial part, with dependencies and meta tags
  • the file app-outro.html contains the template and code for the dynamic execution
  • the file outro.html contains only the closing tags for the static page
  • a fourth part, the static one, is generated during the building process and used to assemble the full static page

Build process

This part is a shame since I’m still struggling with tools like gulp, coping with weird errors and unwanted results.

Again, I needed things done and I got them using the good old GNU Make. I’m not proud of it and I definitely need some help on this side, but on the other hand, the deadline was met so I’m happy with it.

A much better link

A much better link