Vue.js教程: 构建一个预渲染SEO友好的应用示例 [译]

做者:Maxime Laboissonnierecss

原文地址: Vue.js Tutorial: An Example to Build and Prerender an SEO-Friendly Sitehtml

译者:jeneser前端

快速了解?直接前往教程步骤或Github仓库&在线演示vue

“我受不了了!咱们的内部报告面板太烂了”jquery

产品经理很生气。他从这个即将崩溃的应用程序中拉取数据的操做是灾难性的。webpack

“Max,咱们须要更好的报告。你能修吗?”laravel

“老实说,我更愿意创建一个全新的应用”,我笑着回答说。git

“好,请便。全权委托,老铁”github

我笑着,搓了搓手。最后,在一个须要使用JS框架的场景中,你们一致选择了Vue.jsweb


最近,我完成该应用的代码,我对它简直爱不释手。
我花了一些时间为社区写了一个vue.js教程,这些教程的灵感所有来自于我最近对vue的实践。在这里,我主要讨论如下两点:

  1. 如何使用Vue.js构建精简的Web应用程序

  2. 如何使用prerender-spa-plugin来处理Vue.js应用的预渲染与SEO

更具体地说,我将带您建立一个小商店,它将具有SEO友好的产品页面。我会提供在线演示以及相关代码。
在咱们开发的最新版Headless CMS中我接触过一些vue,这一次咱们会更加的深刻,我很兴奋!

更新:咱们正在将Snipcart的前端从Backbone迁移到Vue.js,了解更多

咱们先来为那些不熟悉渐进式框架(Vue.js)的同窗作一下简单的介绍。

Vue.js究竟是什么?

Vue.js

Vue.js是一套帮助你构建用户界面的轻量级,渐进式的JavaScript框架

不要被“JS框架”这必定义所愚弄。Vue与目前流行的React.js & Angular.js是大相径庭的。对于初学者来讲,它不是Google&Facebook等商业技术巨头的开源副产品。

Evan You(尤雨溪)在2014年首次发布了它,旨在建立一个“增量开发”的现代JS库。Vue最强大的功能之一是:建立可复用的组件,你能够在其余项目中重用这些组件而不用再次编写。全部开发人员均可以在项目中尝试Vue,而不用担忧这会对现有的代码库产生危害或是增长额外的负担。

抛开模式和术语,我以为Vue有如下提论:

1. 一开始你不知道整个应用的架构状态
2. 应用数据必定会在运行时发生改变

正是围绕这些提论,vue塑造了自身:它是渐进式,基于组件和响应式的。组件的粒度划分可让你轻松地分离应用逻辑,同时又保持它们的可重用性。更重要的是,它将您的数据原生绑定到视图,以便在须要时“神奇”地更新(经过watcher)。虽然许多响应式前端框架拥有一样的功能,可是我发现Vue更优雅的实现了它,而且,对于个人大多数用例,它每每表现的更好。

Vue还具备更加平滑的学习曲线,对于React来讲,咱们须要掌握JSX模板等的相关知识。甚至能够说Vue是React减去了比较复杂的部分。

Vue官方文档提供了与其余JS框架(React, Angular, Ember, Knockout, Polymer, Riot)更加深刻的对比。查看官方文档

最后但一样重要的是:得益于高性能&强大的开发工具,Vue为咱们提供了最佳的编码体验。它能如此流行也就不足为奇了!

vuejs流行度

从开源项目LaravelPageKit,到企业,如GitlabCodeship(更不用说阿里巴巴和百度这些巨头了),许多组织正在使用Vue。

OK,如今是时候来看看咱们将如何使用它了。

Vue.js例子:一个快速的,搜索引擎友好的电子商务应用

在本节中,我会告诉你如何使用Vue 2.0 & Snipcart创建一个小型的电子商务应用程序。咱们还将看到如何确保产品页面被搜索引擎正确“抓取”。

准备

若是你想深刻了解 Vue 2.0 相关知识,能够查看Laracasts上的这个系列

1. 环境配置

首先,咱们将使用vue-cli来构建基本的Vue应用程序。在你喜欢的终端里,输入:

npm install -g vue-cli
vue init webpack-simple vue-snipcart

这将建立一个新的vue-snipcart文件夹,其中包含使用vue-loader的基本配置,它将能使咱们编写单文件组件(template/js/css在同一个.vue文件中)。

咱们但愿这个示例尽量真实,所以,咱们将在本应用中增长两个普遍应用于大型项目的模块:vuexvue-router

  • vuex是类Flux架构的状态管理器 - 轻量级,很是强大。它受到了Redux的影响,你能够在这里了解更多

  • vue-router容许您定义路由以动态处理应用程序的组件。

要安装这些,请先进入vue-snipcart项目文件夹,而后运行如下命令:

npm install --save vue-router
npm intsall --save vuex

接下来要安装的是prerender-spa-plugin,这将使咱们可以预渲染“蜘蛛”将要爬行的路径:

npm install --save prerender-spa-plugin

快要完成了,最后四个包:

  • pug - 模板引擎,相对于HTML我更喜欢它。

  • vuex-router-sync-to - 轻松保持vue-router和vuex存储同步。

  • copy-webpack-plugin-to - 轻松包含咱们在dist文件夹中的静态文件。

  • babel-polyfill - 在PhantomJS中运行Vue(经过咱们的预渲染插件使用)。

运行这些:

npm install --save pug
npm install --save vuex-router-sync
npm install --save copy-webpack-plugin
npm install --save babel-polyfill

2. 架构

安装完成后请检查是否安装正确。以后,即可以处理咱们的商店数据了。

先从vuexstore开始,咱们将使用它来存储/访问咱们的产品信息。

在本演示中,咱们将使用静态数据,若是咱们要取而代之,它仍然能够工做。

注:关于Snipcart,咱们使用基本的JS代码段注入购物车,并使用简单的HTML属性定义产品。

2.1 构建store

src中建立一个store文件夹,包含如下3个文件:

  • state.js - 定义咱们的静态产品数据

  • getters.js - 定义get函数,经过ID检索产品

  • index.js - 组合前两个文件

//state.js
export const state = {
    products: [
        {
            id: 1,
            name: 'The Square Pair',
            price: 100.00,
            description: 'Bold & solid.',
            image: 'https://snipcart.com/media/10171/glasses1.jpeg'
        },
        {
            id: 2,
            name: 'The Hip Pair',
            price: 110.00,
            description: 'Stylish & fancy.',
            image: 'https://snipcart.com/media/10172/glasses2.jpeg'
        },
        {
            id: 3,
            name: 'The Science Pair',
            price: 30,
            description: 'Discreet & lightweight.',
            image: 'https://snipcart.com/media/10173/glasses3.jpeg'
        }
    ]
}

//getters.js
    export const getters = {
        getProductById: (state, getters) => (id) => {
            return state.products.find(product => product.id == id)
        }
    }

//index.js
import Vue from 'vue'
import Vuex from 'vuex'
import { state } from './state.js'
import { getters } from './getters.js'

Vue.use(Vuex)

export default new Vuex.Store({
  state,
  getters
})

2.2 构建路由器

咱们将保持商店尽量简单:展现产品列表的首页以及每一个产品的详细信息页面。咱们须要在路由器中注册两条路由来处理这些路由:

import VueRouter from 'vue-router'
import Vue from 'vue'
import ProductDetails from './../components/productdetails.vue'
import Home from './../components/home.vue'

Vue.use(VueRouter)

export default new VueRouter({
  mode: 'history',
  routes: [
    { path: '/products/:id', component: ProductDetails },
    { path: '/', component: Home },
  ]
})

咱们尚未建立这些组件,不用担忧,立刻就来,;)

请注意,咱们在VueRouter声明中使用了mode:'history'。这一点很重要,不然咱们的prerender插件将不会工做。其区别在于路由器将使用history API而不是hashbang来导航。

2.3 把全部东西组合在一块儿

如今,咱们有了数据(store)和路由器,咱们须要把他们注册到应用中。更新你的src/main.js文件:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import { sync } from 'vuex-router-sync'
import store from './store'

sync(store, router)

new Vue({
  store,
  router,
  render: h => h(App)
}).$mount('#app')

很简单吧!正如前面提到的,vuex-router-sync中的sync方法从咱们的store中注入状态到当前的路由中。咱们稍后再用。

3. 书写Vue组件

有数据感受真棒,但将它显示出来将会更好。咱们即将用到的三个组件:

  • Home - 展现产品列表

  • Product - 单个产品信息,将被用在Home组件中

  • ProductDetails - 产品详情页

他们将被包含在src/components文件夹中。

//Home.vue

<template lang="pug">
    div(class="products")
        div(v-for="product in products", class="product")
            product(:product="product")
</template>

<script>
import Product from './../components/Product.vue'

export default {
  name: 'home',
  components: { Product },
  computed: {
    products(){
      return this.$store.state.products
    }
  }
}
</script>

以上,咱们使用store中的状态来获取咱们的产品,并对它们进行迭代,来渲染每个产品。

//Product.vue
<template lang="pug">
  div(class="product")
   router-link(v-bind:to="url").product
      img(v-bind:src="product.image" v-bind:alt="product.name" class="thumbnail" height="200")
      p {{ product.name }}
    
    button(class="snipcart-add-item"
      v-bind:data-item-name="product.name"
      v-bind:data-item-id="product.id"
      v-bind:data-item-image="product.image"
      data-item-url="/"
      v-bind:data-item-price="product.price")
        | Buy it for {{ product.price }}$
 
</template>

<script>
export default {
  name: 'Product',
  props: ['product'],
  computed: {
    url(){
      return `/products/${this.product.id}`
    }
  }
}
</script>

经过路由器,咱们连接到其余页面(ProductDetails),来看看咱们的最后一个组件:

//ProductDetails.vue
<template lang="pug">
  div(class="product-details")
    
    img(v-bind:src="product.image" v-bind:alt="product.name" class="thumbnail" height="200")
     
    div(class="product-description" v-bind:href="url")
      p {{ product.name }}
      p {{ product. description}}

      button(class="snipcart-add-item"
        v-bind:data-item-name="product.name"
        v-bind:data-item-id="product.id"
        v-bind:data-item-image="product.image"
        data-item-url="/"
        v-bind:data-item-price="product.price")
          | Buy it for {{ product.price }}$

</template>

<script>
export default {
  name: 'ProductDetails',
  computed: {
    id(){
      return this.$store.state.route.params.id
    },
    product(){
      return this.$store.getters.getProductById(this.id)
    }
  }
}
</script>

这一节的逻辑要稍微复杂些:咱们从路由中获取当前的ID,而后经过以前建立的getter获取相关的产品信息。

4. 建立App

咱们开始使用刚才建立的组件。

打开App.vue文件,其内容是脚手架(vue init webpack-simple)生成的默认内容。咱们来修改它:

<template lang="pug">
  div(id="app")
    TopContext
    router-view

</template>

<script>
import TopContext from './components/TopContext.vue'

export default {
  name: 'app',
  components: { TopContext }
}
</script>

TopContext组件不是很重要,它仅仅是一个header。关键部分是router-view:它将经过VueRouter动态加载组件,而以前与之关联的组件将被替换。

最后咱们来更新一下index.html。对于咱们的用例来讲,咱们在src中建立新的目录static,移动index.html文件至static并将其更新为以下内容:

<!DOCTYPE html><html lang="en">
  <head>
    <meta charset="utf-8">
    <title>vue-snipcart</title>
  </head>

  <body>
  
    <div id="app">    
    </div>
  
    <script src="/build.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
    <script src="https://cdn.snipcart.com/scripts/2.0/snipcart.js" data-api-key="YjdiNWIyOTUtZTIyMy00MWMwLTkwNDUtMzI1M2M2NTgxYjE0" id="snipcart"></script>
    <link href="https://cdn.snipcart.com/themes/2.0/base/snipcart.min.css" rel="stylesheet" type="text/css" />
  </body>
</html>

你能够看到,咱们在index.html中添加了Snipcart的必要脚本。若是将他们精细的划分到各个组件之中代码看起来会更加干净,但,因为咱们全部的View都须要它们,咱们便这样作了。

5. 使用Prerender插件处理Vue.js SEO

vuejs seo prerendering

咱们应用中的全部内容都是使用JS动态渲染的,这很不利于搜索引擎优化(SEO):网页中的异步内容不能被“蜘蛛”(search engine bots)有效的识别并抓取,这样的话,咱们的电子商务网站错过了全部有用的“网络爬虫”,这不是一个明智的选择!

让咱们使用prerendering技术来为咱们的Vue.js应用程序带来更多的SEO机会。

相对于Vue的SSR(服务器端渲染),prerendering则更容易使用。坦率地说,前者有些矫枉过正了,除非你有大量的路由要处理。另外,这两种技术在实现SEO层面所达到的效果是类似的。

预渲染将使咱们可以保持咱们的前端做为一种快速,轻量级的静态网站,以便于“蜘蛛”进行爬取。

让咱们来看看如何使用它:转到WebPack配置文件,在plugin配置项中添加如下配置:

plugins: [
  new CopyWebpackPlugin([{
    from: 'src/static'
  }]),
  new PrerenderSpaPlugin(
    path.join(__dirname, 'dist'),
    [ '/', '/products/1', '/products/2', '/products/3']
  )
]

好吧,它是如何工做的呢?

CopyWebpackPlugin将会复制static文件夹中的文件到dist文件夹中(只包含引用Vue App的应用程序的视图)。而后,PrerenderSpaPlugin使用PhantomJS加载网页的内容,并将结果做为咱们的静态资源。

瞧!咱们如今已经为咱们的Vue应用程序提供了预渲染的,SEO友好的产品页面。

咱们使用以下命令来进行测试:

npm run build

这将生成一个dist文件夹,其中包含生产环境所需的一切。

其余重要的SEO因素

  1. 考虑为您的页面添加适当的meta标记和站点地图(sitemap)。您能够在“postProcessHtml”函数(prerender-spa-plugin插件的配置项)中了解有关meta标记的更多信息

  2. 恰当的内容在现代SEO中起了重要做用。建议您确保应用程序中的内容易于建立,编辑和优化。为了受权内容编辑者,请考虑将headless CMS放入组合中并用来构建真正的JAMstack

  3. 如今,HTTPS链接正式成为Google的排名因素。咱们在Netlify上托管这个演示,Netlify为咱们提供了免费的SSL证书。

  4. Mobile-first indexing和 mobile-friendliness也是排名的重要因素。确保您的移动体验与桌面版同样快速完整!

GitHub库和在线演示

vuejs-tutorial-live-demo.png

来吧,这里是在线演示及代码仓库的地址!

GitHub仓库

在线演示

总结

我以前使用过Vue,本教程的制做过程仍是至关顺利的。我花了一个小时在Demo上,在使用CopyWebpackPlugin时遇到了困难,好在我在他们的文档中找到了答案。

我但愿这篇文章能鼓励开发人员在一些项目中开始使用Vue。就像我说的,您能够经过开发一个现有项目的一小部分来逐步地开始,我认为这绝对值得一试。咱们的开发主管正在使用Vue编写最新的商业仪表盘功能,他很是喜欢Vue。另外,若是配置正确,Vue彻底能够驱动具备良好SEO结果的应用程序。

若是你受到了启发,能够看看Awesome-vue,它包含了Vue示例和相关项目。

若是你真的喜好Vue,cop some swagsupport the creator


若是你以为这篇文章有价值,请花一点时间分享到Twitter上。有什么遗漏或错误的?有关于Vue的?或其余框架处理SEO的一些想法?如今评论区是你的了!

End

做者:Maxime Laboissonniere

原文地址: Vue.js Tutorial: An Example to Build and Prerender an SEO-Friendly Site

译者:jeneser

译者GitHub:https://github.com/jeneser

版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证

勘误&讨论: New issue