SEO Friendly Single-Page Apps with Batman.js and Rails
Getting search engines to crawl your wonderful single-page Website is hard. Since rendering is not done by the server, you have no real way of serving your site content to non-browser clients (like search engine robots).
Some ways of dealing with this challenge include:
- Running a headless browser that intercepts search-engine requests and mimics the behaviour of a normal (human) user.
- Duplicating views from your single-page app to the server, or creating separate views and serving them for search engines.
There is, however, a much simpler solution, that requires surprisingly little code, and can probably be adapted to a few of the popular single-page MV* frameworks out there.
Sounds interesting? Well, read on to find out more.
Batman.js and HTML templates
Batman.js is my framework of choice. I won’t list all the reasons here, but there’s one feature Batman.js has, that makes if a very compelling candidate for our SEO-SPA (Single-Page-Application) experiment - HTML-only templates.
Batman’s templates are just HTML with some funky data attributes that contain flow, content and logic. There’s no special template engine (or indeed language) to learn or use. Just pure, simple HTML.
Additionally, Batman’s template usually don’t contain dynamic content. The content is appended by the framework during runtime.
So, a typical Batman template markup will look something like this:
From HTML to ERB
Since Batman.js works really well with Rails, and Rails’ default template engine, ERB, is just magic tags on top of standard markup, what if we were to take the same markup above, and convert it to ERB for server-side rendering.
Well, what do you know, ERB comes in on top of Batman. Batman’s template engine doesn’t need the ERB part, and ERB doesn’t care about Batman’s data attributes.
So, if we could use ERB on the server, to render our templates for search engine consumption, and use the same template, without ERB for Batman’s consumption, we should have a clear winner.
Setting our goals
Let’s try and clarify what we’re actually trying to do here, and then see how we can build on the example above and fulfil the goals below.
Goal 1 - We want a single-page application that is visible to search engines. Specifically, we aim for the search engine to “see” exactly the same markup and styles as normal users (Google screenshots anyone?).
Goal 2 - We’d like to share our HTML templates between our MV* framework (Batman.js in this case) and the server. Sharing templates serves two purposes - Avoiding code duplication and making sure server-side rendering happens as quickly as possible (meaning we don’t want to resort to non-native or headless-browser based server-side rendering).
We’re going to achieve the above goals, using Rails, ERB and Batman.js with the following steps:
- Setup a Rails & Batman.js application
- Add HTML templates and suffix them with ERB for server-side rendering
- Render templates in controllers from View Cache (see View Cache pre-populating below)
- Add ERB code inside Batman’s templates for server side rendering
- Cleanup ERB code in templates and pre-populate them to Batman’s View Cache
- Tell Rails to look for render-able templates inside Batman’s template directory instead of the standard app/views directory
- Inside Rails controllers, render either ERB template for search engine or JSON for XHR coming from our Batman.js SPA.
Assuming we have a Rails & Batman.js application installed, here are the important bits that follow the steps above.
First, we add Batman’s template directory to Rails’ view path, so our Batman templates can be rendered by Rails’ controllers.
Next, we add a view helper that will take our Batman, ERB-fuelled templates and clean up and ERB code, reverting them to their “intended”, ERB-less state. The output of this helper will be used to pre-populate Batman’s View Cache.
The code above is a modified version of the code found in a post titled Batman’s Secret Cache).
Now let’s pre-populate Batman’s View Cache with the ERB-less templates (we’re doing this inside our server-side layout, to ensure changes to our HTML templates are always reloaded):
And finally let’s make sure Rails’ asset pipeline doesn’t try to pre-compile our ERB templates:
To finish things up, let’s look at an example template inside our Batman.js application:
For client-side, single-page rendering mode, our view helper will read that template, strip all ERB tags and save it to Batman’s View Cache.
For server-side, search-engine mode, Rails will simply read and parse the ERB code, and output it as HTML with all the dynamic content coming from the server (in the above example, a list of blog posts).
A Live Example
Since seeing is believing, check out a live demo of the above technique on Heroku.
If you want to pretend to be a search engine, visit http://batsoe.herokuapp.com/?se=1 and view the page source.
Some of you might ask why Batman and Rails and not Angular.js or Backbone.js . Well, the main reason is Batman’s template engine which relies on HTML tags only and the fact that Batman was designed to work well with Rails (so for example, template naming conventions are the same in Batman as they are in Rails). In short, it made sense that if I chose one, I’d choose the other.
The code for the above demo is available on Github. Feel free to fork and spread the word.