Over a million developers have joined DZone.

Adventures in SEO With Vaadin

Vaadin was hardly SEO-friendly in the past. Not anymore, with the new Volga library.

· Web Dev Zone

Start coding today to experience the powerful engine that drives data application’s development, brought to you in partnership with Qlik.

Bookmarking Pages

Bookmarking is as old as www itself. Being able to save a URL is part of the ADN of websites. Regarding web apps, this is somewhat different. For example, in an e-commerce webapp, while it does make sense to bookmark a specific product, bookmarking a specific step of the checkout process does not.

Following the shop example, here’s what happens in a traditional servlet-based context:

  1. A servlet is mapped on a specific subcontext such as /product/*
  2. When the URL /product/really-cool-product is called, the doGet() method of that servlet is called
  3. The method parses the URL to read the really-cool-product part - which should be a unique key for the product
  4. It delegates to a whole chain of components that loads the product from the datastore
  5. It forwards to a JSP along with the relevant product data
  6. This JSP generates the HTML

Single-Page Applications

Come SPAs. By definition, they serve all content under the same URL. This makes bookmarking specific pages of the application impossible because there are no pages per se. In general, SPAs handle this problem with fragment identifiers. The above URL becomes/product#really-cool-product, problem solved. In Vaadin, this directly translates to usage of the Page.getCurrent().setUriFragment()method or of the Navigator API.

Unfortunately, this doesn’t work at all with the crawling part of SEO. Fragments are not discriminatory parts of a URL: #really-cool-product and #another-cool-product do point to the same URL so bots such as Google Bot won’t crawl both.

The fragment identifier functions differently than the rest of the URI: namely, its processing is exclusively client-side with no participation from the web server.
-- Wikipedia

Distinct URLs for SPAs

Back to square one, both /product/really-cool-product and /product/another-cool-product paths are required. This problem is not unique to Vaadin, but common to all server- and client-side SPA frameworks. What is required is:

  1. To have the client change the browser’s URL with no server interaction - no page reload nor AJAX calls
  2. To have the server handle paths

In JavaScript, the answer is to use the History API. I assume everyone is familiar with the following snippet:


This is however absolutely not standard. This should be replaced by the following:


The history object implements the History API. In particular, it API makes it possible to add entries in the browser history via thepushState() method.

"Suppose http://mozilla.org/foo.html executes the following JavaScript:

var stateObj = { foo: "bar" };
history.pushState(stateObj, "page 2", "bar.html");

This will cause the URL bar to display http://mozilla.org/bar.html, but won't cause the browser to load bar.html or even check that bar.html exists." --Mozilla Developer Network

On the server-side, handling different paths is quite trivial - even though some designs are better than others.

Beyond Distinct URLs

Distinct URLs is only the emerged part of the iceberg regarding SEO.

One wants to have dedicated meta headers for each dedicated URL, such as <title> and <meta name="description">. Even further, social medias have their own dedicated meta headers, e.g.:

Volga, the SEO-friendly Vaadin library

Implementing the steps above from scratch in your Vaadin project is definitely not trivial. Rejoice, for here comes Volga, a ready-to-use library that handles the brunt of things for you.

To use it, just add this snippet to your POM:


Important parts of the API include:

  • org.vaadin.volga.VolgaDetails: Holds a set of metadata headers
  • org.vaadin.volga.VolgaServlet: Set the VolgaDetails for the root path and provides bindings between a path and other VolgaDetails objects. This way, each specific path can be set their own VolgaDetails.
  • org.vaadin.volga.Volga: Holds the previously defined mappings
  • org.vaadin.volga.VolgaUI: Handles the initial configuration
  • org.vaadin.volga.SeoBootstrapListener: Fills page metadata headers from a VolgaDetails object

For more details, please check this example project on Github. It’s deployed online and here are results shown on Google search that proves that it works.

Demo application as seen on Google Search

This works for Twitter as well:

Twitter card for main viewTwitter card for second

Create data driven applications in Qlik’s free and easy to use coding environment, brought to you in partnership with Qlik.


Published at DZone with permission of Nicolas Frankel, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}