Adventures in SEO with Vaadin

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

Bookmarking pages

Bookmarking is as old as www itself. Being able to save an 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 an 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 translate 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 an 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 the pushState() 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 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 part of the API include:


Holds a set of metadata headers


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.


Holds the previously defined mappings


Handles the initial configuration


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 view Twitter card for second

Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Also double as a trainer and triples as a book author.

Read More
Adventures in SEO with Vaadin
Share this