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

相關文章
相關標籤/搜索