Back to Posts

Integrating A Rails Blog With Facebook Instant Articles

Posted in ruby

On a recent project, a client asked for a dynamic website and a blog. Since most of their interaction with customers happened through their Facebook Page, they asked for a number of integration between their new site and Facebook. Aside from the usual like, share, and comment stuff, it was an opportunity to play with the new Instant Article format.

After reading Facebook’s documentation, it seemed the simplest way was to create a good old RSS feed and hand it to the Facebook Page. Here is the corresponding article from their documentation.

In this post I will focus only on creating that feed from an existing blog. If you’re interested, this minimal example is on GitHub.

Essentially the steps were:

  • Create an instant_articles controller with an index and a show action
  • Create the show template with all the markup necessary for Facebook to create an Instant Article.
  • Create an index template for the RSS feed containing every article.

The models

Before diving in, this example will use an Article and an Author model with the following attributes:

Article

  • belongs_to an author
  • has a title, a subtitle, a kicker, and a body
    The body should be in HTML, so you should probably use a WYSIWYG editor if you’re building for a client. One caveat is that Facebook basically only parses <p> and <figure> tags for the body of the article. In my case I used trix, which defaults to using <div> tags for the content. It took me some time to figure out why Facebook was complaining that the Instant Articles were empty. The solution was to build trix from source with the correct config flag. If you use another editor, YMMV.
  • has a cover_url and a cover_description
    For simplicity here, I defined a cover_url method on the Article model which points to unsplash.it. Attaching an image to a Rails model is beyond the scope of this post. Checkout Paperclip or Carrierwave if you need that.

Author

  • has_many articles
  • has a name and a bio

The controller

app/controllers/instant_articles

1
2
3
4
5
6
7
8
9
10
class InstantArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    article = Article.find(params[:id])
    render layout: false, locals: { article: article }
  end
end

Pretty conventional, except for the show action. We need to pass the article as a local variable to the render method instead of using an instance variable. The reason for that will be clear in the index.rss.builder template where we will need to render the show template again.

The show template

This is based on a sample code from Facebook docs. You can find it here.

app/views/show.html.erb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!doctype html>
<html lang="en" prefix="op: http://media.facebook.com/op#">
  <head>
    <title>My Blog | <%= article.title %></title>
    <meta charset="utf-8">
    <link rel="canonical" href="<%= article_url(article.id) %>">
    <meta property="op:markup_version" content="v1.0">
  </head>
  <body>
    <article>
      <header>
        <h1><%= article.title %></h1>
        <h2><%= article.subtitle %></h2>
        <time class="op-published" datetime="<%= article.created_at.iso8601 %>">
          <%= article.created_at %>
        </time>
        <address>
          <%= link_to article.author.name, article.author %>
        </address>
        <figure>
          <img src="<%= article.cover_url %>" alt="<%= article.cover_description %>">
          <figcaption><%= article.cover_description %></figcaption>
        </figure>
        <h3 class="op-kicker"><%= article.kicker %></h3>
      </header>
      <%= article.body.html_safe %>
      <footer>
        <aside>
          <p><%= article.author.name %></p>
          <%= article.author.bio.html_safe %>
        </aside>
      </footer>
    </article>
  </body>
</html>

A few things to note here. First, make sure there is a link to the original content in the <head>. Second, Facebook expects <time> to have its datetime attribute in a specific format (ISO 8601).

Again, to make things simple here, I used the created_at time stamp for the date, but you’ll probably want to create a published_at attribute instead, and add another time stamp for updated_at, so that Facebook knows when an article is modified.

Now let’s wrap this all together and create our RSS feed.

The index template

This is what we want. The goal is to create the same XML structure as this.

app/views/index.rss.builder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
xml.rss version: '2.0', 'xmlns:content' => 'http://purl.org/rss/1.0/modules/content' do
  xml.channel do
    xml.title 'My Blog'
    xml.link articles_url
    xml.description 'Instant Articles from my blog.'
    xml.language 'en-us'

    @articles.each do |article|
      xml.item do
        xml.title article.title
        xml.link article_url(article.id)
        xml.content :encoded do
          xml.cdata! render(template: 'instant_articles/show', locals: { article: article }, formats: :html, layout: false)
        end
        xml.guid article_url(article.id)
        xml.pubDate article.created_at.iso8601
        xml.author article.author.name
      end
    end
  end
end

The magic happens at line 13, where we render the show template above.

Watch out for the format of the time stamps, ISO 8601 again.

Takeaway

There are more things you could/should do to complete the integration, such as creating rich media elements, , configuring a news feed preview, or creating a style. I definitely recommend checking the documentation, it’s pretty extensive and it has code samples.

Using the RSS feed to import articles is not optimal if you want immediate feedback on the import process. For that you could use the Instant Articles API. But the RSS feed integrates nicely with the publishing tools, giving you a nice and easy way to edit your Instant Articles on Facebook and scheduling them to be shared on the page.