Vue.js教程: 構建一個預渲染SEO友好的應用示例 [譯]

作者:Maxime Laboissonniere
原文地址: Vue.js Tutorial: An Example to Build and Prerender an SEO-Friendly Site
譯者:jeneser

快速了解?直接前往教程步驟或Github倉庫&在線演示

“我受不了了!我們的內部報告面板太爛了”

產品經理很生氣。他從這個即將崩潰的應用程序中拉取數據的操作是災難性的。

“Max,我們需要更好的報告。你能修嗎?”

“老實說,我更愿意建立一個全新的應用”,我笑著回答說。

“好,請便。全權委托,老鐵”

我笑著,搓了搓手。最后,在一個需要使用JS框架的場景中,大家一致選擇了Vue.js


最近,我完成該應用的代碼,我對它簡直愛不釋手。
我花了一些時間為社區寫了一個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 install --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  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 indexingmobile-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許可證

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,514評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,373評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,975評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,743評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,199評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,414評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,951評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,780評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,218評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,649評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,889評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,673評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容