Better SEO with GWT

When we started working on our new website, we quickly faced challenges often encountered with single-page application frameworks: enabling search engine optimization (SEO) and crawlability. The problem with these kinds of applications is that you only have one .html host file, which has two important consequences:

  1. Having only one title tag makes it hard for people to use the browser history for navigation
  2. It becomes impossible to define meta properties with relevant data for different site sections

Thankfully, each of these problems can be solved.¨

Changing the title

This is actually a simple fix. We can use GWT’s Document class to change the title when our section is attached to the DOM:


public class MyViewImpl implements MyView, AttachEvent.Handler {
interface Binder extends UiBinder<Widget, MyViewImpl> {}
private final Widget widget;
@Inject
MyViewImpl(Binder binder) {
widget = binder.createAndBindUi(this);
widget.addAttachHandler(this);
}
@Override
public void onAttachOrDetach(AttachEvent event) {
if (event.isAttached()) {
Document.get().setTitle("This is the new title of the page");
}
}
// …
}

view raw

MyViewImpl.java

hosted with ❤ by GitHub

Adding and updating meta properties

Crawler

The hard way

We could create MetaElements and inject them into the head section manually when our section is attached to the DOM :


public class MyViewImpl implements MyView, AttachEvent.Handler {
interface Binder extends UiBinder<Widget, MyViewImpl> {}
private final Widget widget;
@Inject
MyViewImpl(Binder binder) {
widget = binder.createAndBindUi(this);
widget.addAttachHandler(this);
}
@Override
public void onAttachOrDetach(AttachEvent event) {
if (event.isAttached()) {
Document.get().setTitle("This is the new title of the page");
updateMetaTags();
}
}
private void updateMetaTags() {
injectMetaProperty("description", "This is the description");
injectMetaProperty("og:image", "http://placehold.it/350×150&quot;);
// …
}
private void injectMetaProperty(String property, String value) {
Document document = Document.get();
MetaElement metaElement = document.createMetaElement();
metaElement.setName(property);
metaElement.setContent(value);
document.getHead().insertFirst(metaElement);
}
// …
}

view raw

MyViewImpl.java

hosted with ❤ by GitHub

Using GWT-SEO

This seems a bit cumbersome and isn’t quite what webmasters are used to, so perhaps we can do better. This is where GWT-SEO comes into play. It allows us to define meta properties in the UiBinder’s ui.xml file as a Widget, almost like we would define them in an html file. The library will then create and inject the defined meta elements in the head section when your View is attached to the DOM, removing the need to register your own AttachHandler.


<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent"&gt;
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:seo="urn:import:com.arcbees.seo.widget">
<g:HTMLPanel>
<seo:SeoWidget>
<seo:Title>My Title</seo:Title>
<seo:Description>My Description</seo:Description>
<seo:Keywords>kw1,kw2,kw3</seo:Keywords>
<seo:Og>
<seo:OgImage height="480" width="480">http://some.image.png</seo:OgImage>
<seo:OgType typeValue="WEBSITE"/>
</seo:Og>
<seo:Custom property="og:audio">http://example.com/bond/theme.mp3</seo:Custom>
<seo:FbAppId>1234567</seo:FbAppId>
</seo:SeoWidget>
<p>This is my page, now with SEO!</p>
</g:HTMLPanel>
</ui:UiBinder>

Now that’s better! Notice that the title is also handled, which means no more document.setTitle() is needed either!

Internationalization (i18n)

Moreover, since we’re within a UiBinder, we can therefore take advantage of the power of ui:msg to internationalize our properties (more details on using GWT i18n with UiBinder in an upcoming post!). In the meantime, you can take a look at our website for a live example of GWT-SEO using i18n, and especially at the sources of the website to see more in details how it was done, or at the source of my bee page for a specific example.


<seo:SeoWidget>
<seo:Title>
<ui:msg description="SEO – MAIN – TITLE">Title</ui:msg>
</seo:Title>
<seo:Description>
<ui:msg description="SEO – MAIN – DESCRIPTION">Description</ui:msg>
</seo:Description>
<!– Some more tags –>
</seo:SeoWidget>

Dynamic data

More often than not, when implementing a single-page application, the content of the section will depend on some data fetched from an AJAX request (e.g. : a product page in an online store). GWT-SEO can also help you with that, although it requires some Java code in your request’s callback. Let’s say we have a Presenter that retrieves a Product when the product’s dynamic page is loaded, and passes it to its View through the setProduct method.


import com.arcbees.seo.Image;
import com.arcbees.seo.OpenGraph;
import com.arcbees.seo.SeoElements;
import com.arcbees.seo.TagsInjector;
// Other imports…
public class MyViewImpl implements MyView, AttachEvent.Handler {
interface Binder extends UiBinder<Widget, MyViewImpl> {}
private final Widget widget;
private final TagsInjector tagsInjector;
@Inject
MyViewImpl(
Binder binder.
TagsInjector tagsInjector) {
widget = binder.createAndBindUi(this);
this.tagsInjector = tagsInjector;
}
@Override
public void setProduct(Product product) {
Photo photo = product.getPhoto();
Image image = new Image(photo.getUrl(), photo.getHeight(), photo.getWidth(), photo.getMimeType());
OpenGraph openGraph = new OpenGraph.Builder()
.withImage(image)
.build();
SeoElements seoElements = new SeoElements.Builder()
.withTitle(product.getName())
.withDescription(product.getDescription())
.withOpenGraph(openGraph)
.build();
tagsInjector.inject(seoElements);
}
// …
}

view raw

MyViewImpl.java

hosted with ❤ by GitHub

The advantage of using GWT-SEO, versus manually creating MetaElements to inject dynamic properties, is that it prevents duplicates by replacing existing properties with new ones, by leveraging the injection of derived tags (title and og:title, description and og:description, etc.).

But what good would all that do if crawlers like Google or Facebook can’t retrieve the fully rendered version of the page?

Making the application crawlable

Making your application crawlable could be the topic of a whole other blogpost, but I will discuss it briefly here.

Most crawlers nowadays implement a simple directive that allows us to serve different content when a hashbang (#!) is encountered in an URL. So the first step is to make sure your section URLs contain hashbangs. For example, a crawler fetching http://www.arcbees.com/#!/support would replace the hashbang with the escaped_fragment query parameter, in this case : http://www.arcbees.com/?_escaped_fragment_=/support (take a look at the rendered DOM).

Rendering for crawlers

Now that we have a way to identify crawlers’ requests, we need to serve a rendered version when we detect the _escaped_fragment_ query parameter to ensure the crawler doesn’t need to run any JavaScript code to access the content. To achieve this, we need something like HtmlUnit or PhantomJS to retrieve our webpage and process the JavaScript until we have a fully rendered page. Or better yet, use GWTP Crawler Service, which handles everything for us, from detecting crawlers’ requests to generating a fully rendered page!

Conclusion

Writing the meta properties for all the sections of your single-page application will allow you to be better ranked on search engines, and give you a chance to stand out against your competitors. It will also make your pages easier to share across social medias, which is, in this 2.0 era, mandatory for any web page. SEO and crawlability are easy things to overlook when developing your application, but to ignore them is to miss out on a substantial number of potential visitors!

Maxime Mériouma-Caron

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s