A Poor Man's Vue-SEO Solution

Or how to postpone implementing a heavyweight solution
author's avatar
Kees de Kooter
Jul 10 2019 16:56 • 2 min read

Suppose you are just starting off with your latest pet project. Vue enabled you to turn your idea into reality. And now you want the search engines to start crawling your brand new site. Of course the landing page is also built with Vue. For regular visitors this page loads fast and responds snappy, thanks to the optimized bundle webpack created for you. Only the web crawlers tend to prefer raw html and don't care what the page looks like visually.

Not willing to spend ages of your precious spare time on a full fledged SSR solution like nuxt or an almost even invasive prerenderer. Why not try to reuse the templates your end user is presented with a touch of NGINX magic added to the mix.

Render static HTML from Vue templates

Include Vue templates

First of all we need to teach Handlebars how to include external files

handlebars.registerHelper('importVueTemplate', (relativePath) => {
  const absolutePath = path.join(__dirname, relativePath)
  const source = fs.readFileSync(absolutePath, 'UTF-8')

  // Peel out the template content
  const regex = /^<template>([^]*)<\/template>/
  let matches = regex.exec(source)
  let body = matches[1]

  // Rewrite <router-link> to anchor
  return body.replace(/router-link/g, 'a')
          .replace(/to=/g, 'href=')
})})

Render markdown content

const handlebars = require('handlebars')
const fs = require('fs')
const path = require('path')
const marked = require('marked')

handlebars.registerHelper('renderMdFile', (relativePath) => {
  const absolutePath = path.join(__dirname, relativePath)
  const source = fs.readFileSync(absolutePath, 'UTF-8')

  return marked(source, {
    sanitize: true,
    breaks: true,
    gfm: true,
    tables: true
  })
})

Handlebars template

<!DOCTYPE html>
<html>
  <head>
    <title>dochub - share the knowledge</title>
    {{! put all of your SEO tags here }}
  </head>
  <body>

    <header>
      {{#importVueTemplate '../src/components/LandingHeader.vue'}}{{/importVueTemplate}}
    </header>

    <main>
      {{#importVueTemplate '../src/components/LandingPage.vue'}}{{/importVueTemplate}}
    </main>
  </body>
</html>

Compile to html in a build step

let source = fs.readFileSync(path.join(__dirname, `/index.hbs`), 'UTF-8')
let template = handlebars.compile(source)

fs.writeFileSync(path.join(__dirname, '../dist/crawlers/index.html'), template({}))

Serve static HTML to web spiders

Now we have to intercept web crawler traffic and redirect this to the static page we just created.

location / {

  root /usr/share/nginx/html;

  # Enable history mode for Vue Router
  try_files $uri $uri/ /index.html;

  # Checking for top 8 web crawlers (see https://www.keycdn.com/blog/web-crawlers)
  if ($http_user_agent ~* (googlebot|bingbot|slurp|duckduckbot|baiduspider|yandexbot|sogou|exabot)) {
      root /usr/share/nginx/html/crawlers;
  }
}

Open issues

  • As we are serving quite crude HTML now, Google might classify the page as not mobile friendly
  • <router-link> tags should be rewritten to <a>