原文連接css
Vue 開發一個單頁面應用,相信不少前端工程師都已經學會了,可是單頁面應用有一個致命的缺點,就是 SEO 極不友好。除非,vue 能在服務端渲染(ssr)並直接返回已經渲染好的頁面,而並不是只是一個單純的 <div id="app"></div>
。html
Nuxt.js 就是一個極簡的 vue 版的 ssr 框架。基於它,咱們能夠快速開發一個基於 vue 的 ssr 單頁面應用。前端
Nuxt.js 官方提供了一個模板,可使用 vue-cli 直接安裝。vue
$ vue init nuxt-community/starter-template <project-name>
node
. ├── README.md ├── assets ├── components ├── layouts ├── middleware ├── node_modules ├── nuxt.config.js ├── package.json ├── pages ├── plugins ├── static ├── store └── yarn.lock
其中:webpack
<nuxt />
標籤中。若是須要在普通頁面中使用下級路由,則須要在頁面中添加 <nuxt-child />
。該目錄名爲Nuxt.js保留的,不可更改。 middleware: 'middlewareName'
。pages: 頁面。一個 vue 文件即爲一個頁面。index.vue 爲根頁面。ios
_id.vue
(以 _
開頭),則爲動態路由頁面,_
後爲匹配的變量(params)。index.vue
。更多的配置請移步至 官網 。根vue.js應用
實例化以前須要運行的 Javascript 插件。須要注意的是,在任何 Vue 組件的生命週期內, 只有 beforeCreate
和 created
這兩個鉤子方法會在 客戶端和服務端均被調用。其餘鉤子方法僅在客戶端被調用。 nuxt.config.js
文件用於組織Nuxt.js 應用的個性化配置,以便覆蓋默認配置。具體配置請移步至 官網。首先,瞭解一下在 nuxt 的頁面中獨有的函數/變量:git
asyncData
方法使得你可以在渲染組件以前異步獲取數據。該方法在服務端中執行的,因此,請求數據時,不存在跨域問題。返回的數據將與 data()
返回的數據進行合併。因爲asyncData
方法是在組件 初始化 前被調用的,因此在方法內是沒有辦法經過 this
來引用組件的實例對象。github
context
變量的可用屬性一覽:web
屬性字段 | 類型 | 可用 | 描述 |
---|---|---|---|
isClient |
Boolean | 客戶端 & 服務端 | 是否來自客戶端渲染 |
isServer |
Boolean | 客戶端 & 服務端 | 是否來自服務端渲染 |
isDev |
Boolean | 客戶端 & 服務端 | 是不是開發(dev) 模式,在生產環境的數據緩存中用到 |
route |
vue-router 路由 | 客戶端 & 服務端 | vue-router 路由實例。 |
store |
vuex 數據流 | 客戶端 & 服務端 | Vuex.Store 實例。只有vuex 數據流存在相關配置時可用。 |
env |
Object | 客戶端 & 服務端 | nuxt.config.js 中配置的環境變量, 見 環境變量 api |
params |
Object | 客戶端 & 服務端 | route.params 的別名 |
query |
Object | 客戶端 & 服務端 | route.query 的別名 |
req |
http.Request | 服務端 | Node.js API 的 Request 對象。若是 nuxt 以中間件形式使用的話,這個對象就根據你所使用的框架而定。nuxt generate 不可用。 |
res |
http.Response | 服務端 | Node.js API 的 Response 對象。若是 nuxt 以中間件形式使用的話,這個對象就根據你所使用的框架而定。nuxt generate 不可用。 |
redirect |
Function | 客戶端 & 服務端 | 用這個方法重定向用戶請求到另外一個路由。狀態碼在服務端被使用,默認 302。redirect([status,] path [, query]) |
error |
Function | 客戶端 & 服務端 | 用這個方法展現錯誤頁:error(params) 。params 參數應該包含 statusCode 和 message 字段。 |
fetch 方法用於在渲染頁面前填充應用的狀態樹(store)數據, 與 asyncData 方法相似,不一樣的是它不會設置組件的數據。爲了讓獲取過程能夠異步,你須要返回一個 Promise,Nuxt.js 會等這個 promise 完成後再渲染組件。
fetch 會在組件每次加載前被調用(在服務端或切換至目標路由以前)。
Nuxt.js 使用了 vue-meta
更新應用的 頭部標籤(Head)
和 html 屬性
。
用於更新 頭部信息。如 title,descripe 等。在 head
方法裏可經過 this
關鍵字來獲取組件的數據。
指定該頁面使用哪一個佈局文件。默認值爲 default
。
須要執行的中間件,如鑑權的 auth
等。
指定頁面切換時的動畫效果。支持傳入 String
, Object
, Function
。具體配置請移步至 官網 。
Nuxt.js 可讓你在動態路由對應的頁面組件中配置一個校驗方法用於校驗動態路由參數的有效性。
返回 true
說明路由有效,則進入路由頁面。返回不是 true
則顯示 404 頁面。
在這裏,咱們使用 CNode API 進行開發 Demo.
請求數據,咱們使用 Nuxt 官方提供的 @nuxtjs/axios 安裝後,在 nuxt.config.js 中加上:
export default { ... modules: [ '@nuxtjs/axios' ], axios: { baseURL: 'https://cnodejs.org/api/v1', // or other axios configs. } ... }
就能夠在頁面中經過 this.$axios.$get
來獲取數據,不須要在每一個頁面都單獨引入 axios.
須要先安裝 sass-loader 和 node-sass
$ yarn add sass-loader node-sass --dev
若是須要在項目中全局使用某個 scss 文件(如 mixins, vars 等),須要藉助 sass-resources-loader : yarn add sass-resources-loader —dev
, 還須要在 nuxt.config.js 的 build 配置中調整導出的 loader 配置:
export default { ... build: { extend(config, { isDev, isClient }) { const sassResourcesLoader = { loader: 'sass-resources-loader', options: { resources: [ // 填寫須要全局注入 scss 的文件。引入後,全部頁面均有效。 'assets/styles/mixins.scss' ] } } // 修改 scss sass 引用的 loader。 config.module.rules.forEach((rule) => { if (rule.test.toString() === '/\\.vue$/') { rule.options.loaders.sass.push(sassResourcesLoader) rule.options.loaders.scss.push(sassResourcesLoader) } if (['/\\.sass$/', '/\\.scss$/'].indexOf(rule.test.toString()) !== -1) { rule.use.push(sassResourcesLoader) } }) } } ... }
首頁通常只須要簡單的獲取首頁數據並渲染便可。
主要 代碼:
asyncData({app, query}) { console.log(query) // 根據不用的標籤獲取不一樣的數據,最後返回話題列表。 return app.$axios.$get(`topics?tab=${query.tab || ''}`).then(res => { // console.log(res) // console.log(JSON.parse(res)) return {list: res.data} }) }
當進入首頁時,該函數會被執行, nuxt 會等到獲取數據後再和組件的 data 合併,進而渲染數據。在模板中,能夠直接使用 list 變量獲取數據。
<div class="card fluid topic" v-for="topic in list" :key="topic.id" > <div class="section"> <h2><nuxt-link :to="{name: 'topic-id', params: {id: topic.id}}" class="topic-title">{{topic.title}}</nuxt-link></h2> <p class="topic-info"> <mark v-if="topic.top" class="tertiary">精華</mark> <mark v-else>{{tabsObj[topic.tab]}}</mark> <span class="avatar"> <img :src="topic.author.avatar_url" alt=""> </span> <span class="username"> {{topic.author.loginname}} </span> </p> </div> </div>
在這裏說起一下, <nuxt-link />
和 <a />
的區別是: nuxt-link
走的是 vue-router 的路由,即網頁已爲單頁面,而且瀏覽器不會重定向。而 a
標籤走的是 window.location.href
,每一次點擊 a
標籤後的頁面,都會進行一次服務端渲染,和普通的 PHP 混合開發沒有太大的區別。
在這裏使用了 nuxt-link
是由於 CNode 的 API 不存在跨域問題,所以能夠做爲一個單頁面應用,體驗更好。
由於列表頁數據類型有多種,該頁面可能會被複用,因此當路由對象發生變化時,須要從新獲取數據,這時能夠監聽路由的變化以作出響應:
watch: { '$route': function() { console.log('$route has changed.') this.getData() } }
配置 seo 優化(這裏只是單純的複製罷了,demo 使用,侵刪):
head() { return { title: '首頁' + (this.$route.query.tab ? `- ${this.tabsObj[this.$route.query.tab]}` : ''), meta: [{ hid: 'description', name: 'description', content: 'CNode:Node.js專業中文社區' }] } }
一樣的,使用 asyncData
函數進行獲取數據,再渲染頁面。
asyncData({app, params}) { console.log(params) return app.$axios.$get('topic/' + params.id).then(res => { // let data = res.data instanceof String ? JSON.parse(res.data) : res.data let data = res.data // console.log(res) // let div = document.createElement('div') // div.innerHTML = res.data.data.content // res.data.summary = div.innerText.substr(0, 120) data.summary = data.content.replace(/<[^>]+>/g,"").substr(0, 120).replace(/\s+/g, '') return {detail: data} }).catch(err => { console.log('axios.get failed.') console.error(err) }) }
在這裏,踩過坑。想使用 div 的 innerText 來過濾掉正文中的 HTML 標籤,可是,若是用戶是直接進入這個頁面的時候,執行 asyncData
時,document
對象是不存在的,從而會報錯。也就是說,當 asyncData
在服務端執行時,是沒有 document
和 window
對象的,請你們注意一下。
做爲一個社區,seo 尤其重要,假若每一個頁面都須要寫一大堆的 head 對象,就會顯得尤爲的繁瑣。因此能夠藉助 nuxt 的 plugin 機制,將其封裝成一個函數,並注入到每個頁面當中:
// plugins/global.js import Vue from 'vue' Vue.mixin({ methods: { // 必傳 標題,描述。其餘的 meta 標籤經過 payload 注入,其中,每一個 meta 的 hid 須要是惟一的。 $seo(title, content, payload = []) { return { title, meta: [{ hid: 'description', name: 'description', content }].concat(payload) } } } })
在 nuxt.config.js 中加上:
export default { plugins: [ '~plugins/global.js' ] }
這樣,只須要在頁面的 head
的函數中,返回該函數便可:
head() { return this.$seo(this.detail.title, this.detail.summary) }
可見,詳情頁已經成功的設置了部分 seo 的標籤。
以上是 Nuxt 的一些基礎配置及應用。
我再去研究一下, fetch 和 store 的結合,將該 demo 繼續完善。