Published Feb 1, 2020

Sections

Intro

This reference guide highlights the steps necessary to setup prerender on a single page app for the purposes of SEO.

It does not go into too much detail in any particular section. It's purpose is to serve more as a checklist and set of examples to point things in the right direction.

This will also be a "living" (maintained) guide that will be updated to reflect any changes in the process.

Also if anyone has examples for their particular setup or comments, etc... Please send a message so that the guide can be updated for others.

Why Prerender

So it is important to point out the benefits of this approach as there are a few ways to approach SEO for SPA apps.

There are two main benefits to this approach.

It doesn't require maintaining a spearate secondary static version of the app for SEO. This technique scans your main app and generates that static version for you. Much less complex approach than Server Side Rending (SSR). With SSR, it requires more setup with additional services to load the content, it's slower, and adds complexity to the code.

Live Demo

An example of this running in the wild can be found at the link below. The first link is the actual Vue.js SPA.

https://starter.websanova.com

Taking a look at the source on any route will show the same static landing index.html file.

Contrast that with the prerender version which has separate static files.

https://prerender-starter.websanova.com

The prerender uses Chromium to go through all the specified routes, scan the rendered content and generates the flat HTML static version of each route.

Sample Repo

Refer to the sample repo for build scripts and example configs.

The sample repo can be found here

Clean URL Paths

The way the prerender works is on routes (or paths). The first step is to make sure all the prerender routes exist.

For the most part this should be fine, but keep in mind that any routes with pagination should use a articles/:page rather than articles?page= format.

Centralize Meta Data

There will be many places where meta data will be required including:

The app itself

Sitemap.xml

Rss.xml

Prerender

It's best to create some kind of meta.js file where changes can propagate out of.

An example meta.js is provided in the sample repo.

With a centralized meta.js file the meta info then just needs to be fed into the corresponding pages.

There are of course many ways to do this depending on the app.

Using Vue

With Vue.js the vue-meta plugin works well enough making it easy to update the pages meta attributes on the fly via a component.

metaInfo() { var meta = keyBy(require('APP/src/router/meta.js'), 'key'); return { title: meta.home.title, meta: [{ vmid: 'keywords', content: meta.home.keywords }, { vmid: 'description', content: meta.home.description }, { vmid: 'og:url', property: 'og:url', content: meta.home.url }, { vmid: 'og:type', property: 'og:type', content: meta.home.type || 'page' }] }; }

App Build

The main app needs to build first.

This will act as the source for the prerender to generate it's files off of.

The only step here is to include a few npm depenenceis for the prerender build.

"devDependencies": { "prerender-spa-plugin": "3.4.0", "webpack": "4.41.2", "webpack-cli": "3.3.10" }

Prerender Build

The build script provided in the sample repo should act as a solid starting point for the prerender build.

There is a build with accompanying prerener.config.js file.

Running The Build

The prerender can run with an optional --mode flag for the environemnt as well.

> node ./build/prerender.js --mode prerender

Sample Prerender Config

The prerender.config.js will get fed directly into the prerender-spa-plugin.

For any configuration options refer to that.

There is only one additional config option added to this file which is the dynamicRoutes option. This allows a url and callback to be set which will fetch any dynamic routes to append to the list of routes for the prerender to process.

Prerender Config Timeout

One thing to be aware of when uisng the prerender-spa-plugin is that the options get fed directly the "rednderer". In the case of this example it is using the Pupetter plugin.

There may be issues with requests timing out if there are too many routes. Make sure to limit the number of routes that the prerender will try to process at once using the maxConcurrentRoutes attribute. The default is set to unlimited which will definitely timeout with many routes.

Optionally there is the timeout setting but that seemed to not work and be ignored.

Page Timeouts

Following from above, another important thing to check is all resources on the page that is being pre-rendered. For instance any images not loading correctly can cause a timeout as it seems the renderer is waiting for all elements to load. It's good to spot check on the live version that it's rendering off of to make sure everything is looking good and there are no lagging or failed requests.

Chromium Dependencies

If there are issues with the prerender running Chromium it's likely due to some missing dependencies.

For Ununtu:

> apt-get install -yq --no-install-recommends libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 libnss3

Sitemap Build

The sitemap build works similar to the prerender except that it generates a basic sitemap.xml file.

It also comes with a build script and it's own config file. The sample repo contains both.

It also contains the dynamicMeta property with a url and callback that can be set to process any dynamic routes into a standardized format for the build to process.

Running The Build

> node ./build/sitemap.js --mode prerender

RSS Build

The rss build follows the exact same process as the sitemap build.

The only difference here is that it contains some additional properties for the rss channel that are fed into the generated rss.xml.

Running The Build

> node ./build/rss.js --mode prerender

Build Summary

In the end the deploy should just run all the builds in sequence.

The final deploy script might look something like this.

npm run build -- --mode production npm run prerender -- --mode production npm run sitemap -- --mode production npm run rss -- --mode production

Prerender Sub Domain

Depending on how the app is setup this section may also differ. Generally it would be good to have some (sub) domain to view the prerender files from.

This helps for inspection of the prerender version of the app that the search bots will see.

This will also serve as the rewrite directory for the bots. Keeping it separate makes it cleaner and a bit easier to manage.

HTTP Bot Rewrite

Once all the builds are running and generating files the final step will be to configure the http service to properly send bots to the prerender directory rather than our dist directory..

Note that this rewrite is only for the prerender files and should not apply to the sitemap or rss which are static files that should be served from the main app's public directory.

Sample Nginx Config

root /path/to/starter.websanova.com; location / { try_files /dist/$uri @prerender; } location @prerender { set $prerender 0; if ($http_user_agent ~* "googlebot|yahoo|bingbot|baiduspider|yandex|yeti|yodaobot|gigabot|ia_archiver|facebookexternalhit|twitterbot|developers\.google\.com") { set $prerender 1; } if ($args ~ "_escaped_fragment_|prerender=1") { set $prerender 1; } if ($prerender = 1) { rewrite .* /prerender$request_uri/index.html break; } if ($prerender = 0) { rewrite .* /dist/index.html break; } }

If using Google Webmaster Tools this will serve as a handy checklist to get any new apps setup there.