Build a Vue.js SEO-Friendly SPA with Prerender & Other Tips

In a rush? Skip to Vue.js prerendering tutorial or live demo.

Do you know about Hulu's JavaScript fail story?

Back in 2016, the online video streaming service experienced a 56% visibility drop due to bad JS handling.

A nightmare no business wants to go through.

enter image description here

To avoid such disasters, you have to know what you're doing when working with modern JavaScript frameworks.

Here at Snipcart, we love Vue.js, but are entirely aware of the SEO issues with a Vue.js singe-page application.

In this post, I want to show JS developers how easy it is to make Vue SEO-friendly.

I'll go through:

  • General SEO tips you should always apply.
  • Specific Vue.js SPA SEO issues.
  • Tools to fix them with server-rendering & prerendering.
  • A technical Vue.js SEO example using prerender-spa-plugin.

This is a fully updated version of our Vue.js SEO resource. The original one was followed by two other pieces about JS frameworks SEO issues, for React, and Angular. There's also a video version of this content you can find at the end of this post.

General SEO tips

Before we explore SEO issues specific to JavaScript SPAs, let's cover the best practices developers should adopt when building search engine optimized sites.

This shortlist is inspired by some of the biggest SEO players such as Moz, Backlinko, and Ahrefs. I've added links to their resources should you want to dig deeper.

→ Meta tags 🏷️

Meta tags are low-hanging fruits. They let you show search engines precisely what your content is about.

However, not all tags are born equal. You should focus your energy on only a few of them, namely:

  • Meta content type—Has to be present on every page, declares your character set for the page.
  • Title—Choose a unique title that catches searchers attention while accurately describing the content of the page. Keep it concise!
  • Meta description—It should convince SERP (Search Engine Results Page) visitors to click on your link to find answers to their specific research intent.
  • Viewport—Essential for a good mobile experience.

Learn more: SEO Meta Tags, by Moz.

vue-meta is a solid tool to manage page meta info in Vue 2.0 components.

Social tags can also have a significant impact on SEO as they help your content to spread on social platforms, thus increasing your SEO social signals. There are specific tags for all the big platforms (Facebook, Twitter, Pinterest, Google+).

Learn more: Must-Have Social Meta Tags for Twitter, Google+, Facebook, and More, by Moz.

→ Mobile optimization 📱

If you're not aware of Google's mobile-first indexing, well, consider this a wake-up call! Google is now giving more love to smooth mobile experiences than desktop ones, and so should you.

Learn more: Mobile SEO: The Definitive Guide, by Backlinko.

NativeScript powers cross-platorm Vue.js mobile apps.

→ HTTPS 🛡️

A missing HTTPS certification or a broken config could penalize your website. Isn't it understandable that search engines are inclined to push sites that are trusted and certified, after all?

Even if it wasn't for SEO, you probably want to offer a platform as secure as possible for your users. So no reason not to meet this criterion!

Learn more: HTTP vs. HTTPS for SEO: What You Need to Know to Stay in Google’s Good Graces by ahrefs.

→ Page speed 🚀

People's attention span is short—searchers are quick to abandon slow-loading pages. Google knows that and, to offer the best UX to searchers, will penalize slow sites.

Googlebots themselves are pretty impatient and won't wait for a script longer than 5 seconds. If you don't meet this timeout, you risk not getting your content rendered adequately. Speed it up!

Learn more: On-Site SEO: Page Speed, by Moz.

→ Sitemap 🗺️

A sitemap act as a map of your site's architecture for searchbots. Included in it should be the pages you consider to be good-quality landing & navigation pages, worthy of indexation.

Sitemaps might not be that useful for smaller websites, but it's still a valuable SEO tool to consider.

Learn more: XML Sitemaps: The Most Misunderstood Tool in the SEO's Toolbox, by Moz.

You can generate a sitemap.xml by vue-router configuration.

→ Link building ⛓️

Building your domain authority remains a key SEO tactic. How does Google know that you've become an authoritative resource? By having other relevant domains linking to yours.

There's no secret formula here, to accomplish this you need to work hard at crafting great content. Content that others want to share and use as a resource on their own site!

The more links you get from relevant sources, the more your authority will rise, the more you'll earn Google favors when it comes to rankings.

Learn more: Link Building for SEO: The Definitive Guide, by Backlinko.

Specific Vue.js SPA SEO issues

vue-js-seo-demo

For the following tutorial, I decided to use the blog demo we made in an earlier post because, honestly, it looks awesome. If you're interested in knowing how it was built, see this post.

Although it's a great piece of work visually, it's not set up ideally SEO-wise. It's a complete SPA that search engines might have a hard time crawling & indexing.

Why is that?

A single-page application adds content to pages dynamically, which has its benefits, but brings two significant issues:

  1. We don't really know to which extent Google is able to crawl and correctly render JS. They've been saying for a few years now that Googlebots are capable of doing so. However, these claims are always followed by a "but" or two.
  2. Google isn't the only search engine out there. If things are still shaky on its side, we know for a fact that the other players out there don't crawl JS yet, thus not encountering the actual content of the page.

So there's no way around it. If you want to make sure your Vue.js website/app ranks on SERPs, you have to act on it.

Your options?

Server-side rendering

With a SSR setup, you have the rendering logic done directly in the backend, in a Node.js environment. HTML views are then returned to the client, ready to be served to search bots.

It's excellent for time-sensitive apps where you want to offload as much logic as possible on the server, but comes with a cost. You're going to need a robust infrastructure to handle the stress added to the server, asking for more development time. You might also slow down your system in the process.

But if you can handle it, it's definitely the way to go for bigger apps. Nuxt.js is without a doubt the tool to use for your server-rendered Vue.js SPA.

nuxt-js-schema

Nuxt.js rendering process [source]

Prerendering

Sometimes though, server-side rendering might feel overkill, like in my demo's case. For a small SPA with only a few pages, prerendering will do the trick just fine. Even more so if your only concern is SEO.

This way, there's no need to attach your Vue.js app to any server. Rendering is done client-side with the use of third-party plugins such as:

  • Prerender.io - Compatible with all the most popular JS frameworks, including Vue.js.
  • prerender-spa-plugin - A webpack plugin that will compile your pages into static pages. Makes all the content available in the source, and indexing is a breeze.

The latter is very easy to use and was build by a Vue.js core team member, so it suits my use case perfectly here.

Vue.js SEO technical example using prerendering

vue-js-seo-prerender-demo

Time to fix the Vue.js blog shown earlier. Using prerender-spa-plugin, I'll have a Vue.js SPA that's in Google's good graces in no time.

Prerequisites

  • Basic knowledge of Vue.js
  • Node.js & npm installations

1. Installing prerender-spa-plugin

Configuring the plugin is very easy. In this setup, I'll run the plugin only when building for production. You don't need prerendering when actively developing components.

Start by installing the plugin from npm.

npm install prerender-spa-plugin

Then, open the build/build.js file. There, you'll find custom things for production build. You'll need to import the plugin and add these lines at the top of the file:

// /build/build.js
const PrerenderSPAPlugin = require('prerender-spa-plugin');
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;

Why PrerenderSPAPlugin.PuppeteerRenderer? Because you'll need to customize the renderer. You need to inject some data to let the application know when it's being prerendered. There are things like Disqus comments that you won't be able to prerender.

In a 100% SEO-friendly app it would be preferable to benefit from these comments as rendered content on your pages. If your working with a static site generator, you should consider Staticman for static user-generated content.

You'll need to fetch all your blog entries and generate a list of routes that will be prerendered. In a real production scenario, you'd probably want to call your headless CMS API or an external service that provides your content. In this case, the data lives in simple JSON files.

Read the feed.json file and generate your routes:

// /build/build.js

const fileContent = fs.readFileSync(path.resolve(__dirname, '..', 'static', 'api', 'feed.json'));
const feed = JSON.parse(fileContent);
const prenderedRoutes = feed.results.map(x => `/read/${x.id}`);

prenderedRoutes.unshift('/');

Add the plugins to webpack configuration:

// /build/build.js

webpackConfig.plugins.push(
  new PrerenderSPAPlugin({
    staticDir: path.resolve(__dirname, '..', 'dist'),
    routes: prenderedRoutes,
    renderer: new Renderer({
      injectProperty: '__PRERENDER_INJECTED',
      inject: {
        prerendered: true
      },
      renderAfterDocumentEvent: 'app.rendered'
    })
  }));

You'll have access to the window.__PRERENDER_INJECTED.prerendered variable when the site is prerendering. This will be useful to exclude content you don't want to prerender.

You also need to specify the wait for the app.rendered event.

In this demo, I'll use Pace, which is an awesome library to add a progress bar on a site quickly. However, you need to make sure the loading is complete before prerendering the site if not, you'll end up getting a random progress bar on multiple pages.

2. Configuring Vue.js for prerender

You'll then need to apply some minor changes in some components. To do so, I changed the way Pace was started. I got rid of the startup.cs file and put everything in main.js.

Open the main.js file and change how your App components are included:

// /main.js

new Vue({
  router,
  render: h => h(App),
  mounted() {
    Pace.start()
    Pace.on('hide', () => {
      document.dispatchEvent(new Event('app.rendered'));
    })
  }
}).$mount('#app')

Once your main component is mounted, start Pace and dispatch the app.rendered event when initial loading is completed.

The last update you have to do is to make sure Disqus is not loaded when the site is being prerendered.

Open BlogPost.vue file where you'll include the Disqus component.

Because of the way the prerender-spa-plugin was configured at first, you'll have access to a variable that indicates you that the site is being prerendered.

Update the showComments method this way:

// /BlogPost.vue

showComments() {
    // This is injected by prerender-spa-plugin on build time, we don't prerender disqus comments.
    if (window.__PRERENDER_INJECTED &&
        window.__PRERENDER_INJECTED.prerendered) {
      return;
    }
    setTimeout(() => {
      this.commentsReady = true
    }, 1000)
}

3. Building your SEO-friendly Vue.js site

You're now ready to build your site. In your terminal use this command:

npm run build

Then, if you look at the dist folder, you should see all the posts in the read folder. A static HTML file has been generated for each post:

dist-folder

That's it! How easy was that, right?

Live demo, Github repo & video tutorial

See the live demo here

See GitHub repo here

For a visual presentation of this content:

Closing thoughts

prerender-spa-plugin is a neat plugin. It's effortless to configure and flexible enough to handle async use cases like the one we had with Pace.

I spent about 2 hours to figure out how to wire all these things together and update our initial application to support prerendering.

As I mentioned earlier, the plugin is straightforward and best suited for simple applications like a static blog. If you want to build something more complicated, I'd suggest you look at Nuxt instead!


If you enjoyed this post, please take a second to share it on Twitter. Got comments, questions? Hit the section below!

Suggested posts: