作者:Maxime Laboissonniere
原文地址: Vue.js Tutorial: An Example to Build and Prerender an SEO-Friendly Site
譯者:jeneser
“我受不了了!我們的內部報告面板太爛了”
產品經理很生氣。他從這個即將崩潰的應用程序中拉取數據的操作是災難性的。
“Max,我們需要更好的報告。你能修嗎?”
“老實說,我更愿意建立一個全新的應用”,我笑著回答說。
“好,請便。全權委托,老鐵”
我笑著,搓了搓手。最后,在一個需要使用JS框架的場景中,大家一致選擇了Vue.js
。
最近,我完成該應用的代碼,我對它簡直愛不釋手。
我花了一些時間為社區寫了一個vue.js教程,這些教程的靈感全部來自于我最近對vue的實踐。在這里,我主要討論以下兩點:
- 如何使用Vue.js構建精簡的Web應用程序
- 如何使用
prerender-spa-plugin
來處理Vue.js應用的預渲染與SEO
更具體地說,我將帶您創建一個小商店,它將具備SEO友好的產品頁面。我會提供在線演示以及相關代碼。
在我們開發的最新版Headless CMS中我接觸過一些vue,這一次我們會更加的深入,我很興奮!
更新:我們正在將Snipcart的前端從Backbone遷移到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為我們提供了最佳的編碼體驗。它能如此流行也就不足為奇了!
從開源項目Laravel&PageKit,到企業,如Gitlab&Codeship(更不用說阿里巴巴和百度這些巨頭了),許多組織正在使用Vue。
OK,現在是時候來看看我們將如何使用它了。
Vue.js例子:一個快速的,搜索引擎友好的電子商務應用
在本節中,我會告訴你如何使用Vue 2.0 & Snipcart
建立一個小型的電子商務應用程序。我們還將看到如何確保產品頁面被搜索引擎正確“抓取”。
準備
- 了解一些Vue.js相關知識,還不了解?
- 基本了解vuex & vue-router
- 一個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
文件中)。
我們希望這個示例盡可能真實,因此,我們將在本應用中增加兩個廣泛應用于大型項目的模塊:vuex
和vue-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. 架構
安裝完成后請檢查是否安裝正確。之后,便可以處理我們的商店數據了。
先從vuex
的store
開始,我們將使用它來存儲/訪問我們的產品信息。
在本演示中,我們將使用靜態數據,如果我們要取而代之,它仍然可以工作。
注:關于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
我們應用中的所有內容都是使用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因素
- 考慮為您的頁面添加適當的
meta
標記和站點地圖(sitemap)。您可以在“postProcessHtml”函數(prerender-spa-plugin插件的配置項)中了解有關meta
標記的更多信息。 -
恰當的內容在現代SEO中起了重要作用。建議您確保應用程序中的內容易于創建,編輯和優化。為了授權內容編輯者,請考慮將
headless CMS
放入組合中并用來構建真正的JAMstack。 - 現在,HTTPS連接正式成為Google的排名因素。我們在Netlify上托管這個演示,Netlify為我們提供了免費的SSL證書。
- Mobile-first indexing和 mobile-friendliness也是排名的重要因素。確保您的移動體驗與桌面版一樣快速完整!
GitHub庫和在線演示
來吧,這里是在線演示及代碼倉庫的地址!
總結
我之前使用過Vue,本教程的制作過程還是相當順利的。我花了一個小時在Demo上,在使用CopyWebpackPlugin
時遇到了困難,好在我在他們的文檔中找到了答案。
我希望這篇文章能鼓勵開發人員在一些項目中開始使用Vue。就像我說的,您可以通過開發一個現有項目的一小部分來逐步地開始,我認為這絕對值得一試。我們的開發主管正在使用Vue編寫最新的商業儀表盤功能,他非常喜歡Vue。另外,如果配置正確,Vue完全可以驅動具有良好SEO結果的應用程序。
如果你受到了啟發,可以看看Awesome-vue,它包含了Vue示例和相關項目。
如果你真的喜愛Vue,cop some swag 或 support 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許可證)