Pre-rendering SPA for SEO and improved perceived page loading speed

Putting your Single Page Application to the top of the Google search result page (SERP)

At the beginning of this year I have quit consulting and set out to build GO2CINEMA – Fast, simple and secure way to book cinema tickets in the UK. I have done a splendid job making it fast, simple and secure. However, it has come at the cost of SEO.

Results searching for “site:go2cinema.com” do not include titles or descriptions.

Google is claiming to be able to index content generated using JavaScript. I have had no such luck. I even made sure that Fetch as Google service is rendering the pages properly. Despite all of this effort, suggestions in the SERP continue to appear with no content, even after requesting Google to re-index the content.

Rendering pages server-side is hard

Over the years, I have worked on multiple React projects that use server-side rendering. One of the two things have happened in all of the cases:

  • The code base for client-side code and server-side code started to have little in common, e.g. as in the case of react-server.
  • Nearly all code was re-used, but it was slow and fragile, e.g. isomorphic-webpack.

Neither of these options are acceptable. I want free universal rendering without changing a single line of my client-side code.

Pre-rendering pages for SEO

I have started researching options that would allow to serve static HTML (rendered version of the SPA) to the search engines.

In the past, Google has recommended to use _escaped_fragment_ AJAX crawling schema. This approach has been since deprecated. However, some services continue to leverage it, e.g. https://prerender.io/. The good news is that (based on my research and surveying) it appears to work: websites using prerender.io are properly indexed in SERP. However, this approach has several disadvantages:

  • Real-users and crawlers are being served different content. This is dangerously close to Cloaking.
  • Real-users do not benefit from server-side rendered HTML.

Furthermore, in the specific case of prerender.io:

  • The pages are cached for at least 1 day, i.e. no real-time content can be served.
  • The same version of page is cached for all devices. This is relevant if your application is displaying different content/ layout depending on the device viewport size (using JavaScript). In this case, either desktop or mobile clients will need to rebuild their DOM after receiving the initial HTML.
  • It uses PhantomJS v2.1 which has issues understanding modern CSS and JavaScript.
  • It is paid a service.

Finally, a small thing, but if you are building a service that is meant to improve SEO, then you might as well aim for higher scores.

prerender.io website is not doing good in PageSpeed Insights test

Pre-rendering pages for everyone!

Meet ūsus, a Node.js program that:

  • Renders page using Chrome Debugging Protocol (CDP).
  • Extracts CSS used to render the page.
  • Renders HTML with the blocking CSS made asynchronous.
  • Inlines the critical CSS.

ūsus has the following use cases:

  • Produce HTML used to render the page. Used to render single page applications (e.g. React and Angular) to produce a static HTML. This can be used as a replacement of https://prerender.io/. Default behaviour.
  • Extract CSS used to render a specific page. Used to capture the critical CSS. Use --extractStyles option.
  • Produce HTML used to render the page with the critical-path CSS inlined and blocking CSS made asynchronous. Use --inlineStyles option.

You can test ūsus from a command line:

$ npm install usus --global
$ usus --help
$ usus render --url http://gajus.com/
$ usus render --url http://gajus.com/ --inlineStyles
$ usus render --url http://gajus.com/ --cookies foo=bar,baz=qux
# Render emulating the iPhone 6
$ uses render --url http://gajus.com/ --deviceMetricsOverride.deviceScaleFactor 2 --deviceMetricsOverride.fitWindow false --deviceMetricsOverride.height 1334 --deviceMetricsOverride.mobile true --deviceMetricsOverride.width 750

Using ūsus in a Node.js program is equally simple:

import {
render
} from 'usus';

/**
* @see https://github.com/gajus/usus#configuration
*/
const configuration = {}

const css = await render('http://gajus.com/', configuration);

How to use ūsus to pre-render a website?

ūsus is designed to produce a static version of the website using a background job.

Do not use ūsus to render resources at the time of the request; it will be slow. It can be made a lot faster by using process pooling, etc. However, a lot more scalable option is to produce a static version of a web page using a background job and serve user the pre-rendered document.

I am using a background job to generate GO2CINEMA movie and venue pages. A proxy is used to detect whether a pre-rendered

GO2CINEMA is running a background job that renders movie and venue pages. In addition to rendering the pages, it uses ūsus inlineStyles option to optimize the critical rendering path.

Time will show whether this improves results for https://www.google.co.uk/search?q=site:GO2CINEMA.com. Meanwhile, PageSpeed Insights is giving GO2CINEMA a near perfect score.

GO2CINEMA is getting near perfect PageSpeed Insights score

Bonus: rendering device specific pages

Earlier I’ve mentioned that one of the downsides of using prerender.io is that responsive websites (where responsiveness is achieved using JavaScript), get re-rendered on the client-side because of the HTML not matching the application layout rules.

In GO2CINEMA, I am using ūsus to prerender pages for different devices. The web server is using user-agent and https://deviceatlas.com/ (paid service) database to recognize the device and serve a device-specific pre-rendered version of the page.

Can you help me with GO2CINEMA?

GO2CINEMA is my baby. I love working working on it 😍. However, it is my first startup this decade and there are a lot of things I need help with.

If you can give feedback, an SEO advice, a business advice, know an angel investor, know someone who can write an article about GO2CINEMA, make a tweet, invite me to a conference, a radio talk show, etc. or just want to express your support/ curiosity and say “Hi!”, email me at gajus@gajus.com or DM via Twitter, https://twitter.com/kuizinas.