2020-02-01 JavaScript SEO Vue.js
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.
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.
An example of this running in the wild can be found at the link below. The first link is the actual Vue.js SPA.
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.
Refer to the sample repo for build scripts and example configs.
The sample repo can be found here
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.
There will be many places where meta data will be required including:
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.
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'
}]
};
}
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"
}
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.
The prerender can run with an optional --mode
flag for the environemnt as well.
> node ./build/prerender.js --mode prerender
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.
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.
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.
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
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.
> node ./build/sitemap.js --mode prerender
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.
> node ./build/rss.js --mode prerender
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
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.
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.
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.