年中總結,用Nuxt.js寫了幾個月的項目終於修修改改要上線了,不容易啊。
文章會先從為什么使用Nuxt.js進行切入,再討論在使用過程中遇到的問題跟解決的方法。
正文
1、為什么要使Nuxt.js?
在網站需要SEO的時候,前端的開發流程大致分為:會套后台模板跟不會套后台模板分成兩種。
不會套模板就很麻煩,每次寫完頁面交給后台去套,套完前端還要去檢查樣式是不是原來的樣子,有沒有變,再來接接口,所以經常就是在催后台
現在提倡前后端分離,我們應該怎樣即要實現前后端分離又要實現SEO優化呢?
感謝大佬們創造了 vue-server-renderer 插件,具體的使用可查看官網給出的教程。
一句話概括插件的原理:把需要展示出來的數據請求放在了node層去處理,然后讀取相應頁面的配置文件把數據輸出到頁面上。
既然有了這個插件我為什么要用Nuxt.js ?
因為在用插件搭建服務端渲染的時候,無法解決文件熱更新導致整個運行程序崩潰問題
從SPA形式切到Nuxt.js可以無縫切換,沒有什么特別大的難點,前提是你通讀了官方文檔。
PS:如果百度找不到答案,可以去Nuxt.js 的 github 看看 issue ,這里面基本可以找你想要的答案,這個辦法真的非常管用。
基礎應用與配置
創建項目
項目的配置,我選擇的是:
-
服務端:Koa // 如果你想在node層做一些操作的話請選擇node框架更靈活
-
UI框架:None
-
測試框架:Jest
-
Nuxt模式:Universal //這個是服務端渲染的, SPA模式沒有服務端渲染
-
使用集成的 Axios
-
使用 EsLint
環境變量
實際項目中有本地,測試,灰度,生產等環境,使用 @nuxtjs/dotenv 可以幫助我們方便的管理我們的環境變量
安裝
npm i @nuxtjs/dotenv -s
新建一個文件名為 .env的文件,也可以叫其他名字(.env是默認讀取的文件名,可查看官方文檔獲取更多姿勢)
需要去 nuxt.config.js 中配置modules模塊
nuxt.config.js
module.exports = {
modules: [
['@nuxtjs/dotenv'] //這里沒有做其他參數傳入,會默認讀取目錄下的.env文件,如果是叫prod.env,應該寫成['@nuxtjs/dotenv', { filename: '.env.prod' }] ], }
以baseUrl 為例:
DEV_BASE_URL = 'http://xxx.cn' TEST_BASE_URL = 'https://xxx.cn' PROD_BASE_URL = 'https://xxx.cn'
package.json 配置啟動腳本命令 加入變量BASE
{
"scripts":{ "dev": "cross-env NODE_ENV=development BASE=dev nodemon server/index.js --watch server", "build-test": "cross-env NODE_ENV=production BASE=test nuxt build", "start-test": "cross-env NODE_ENV=production BASE=test node server/index.js", "build": "cross-env NODE_ENV=production BASE=prod nuxt build", "start": "cross-env NODE_ENV=production BASE=prod node server/index.js", "generate": "nuxt generate" } }
nuxt.confug.js
require('dotenv').config() let baseUrl = '' switch (process.env.BASE) { case 'dev': baseUrl = process.env.DEV_BASE_URL break; case 'test': baseUrl = process.env.TEST_BASE_URL break; case 'prod': baseUrl = process.env.PROD_BASE_URL break; default: baseUrl = process.env.PROD_BASE_URL break; } module.exports = { env: { baseUrl: baseUrl, // 全局注入環境變量baseUrl,訪問 process.env.baseUrl } }
異步請求
官方推薦使用 @nuxtjs/axios ,雖然少了 axios 的一些api,但是它都強烈推薦了,這坑還是不要踩了。
But,我找了半天,它沒有跟說怎么在項目里進行封裝啊,難道把所有的接口都寫在plugins里進行全局注入?emmm,所以我又用了 axios。如果你們知道請告訴我。
asyncData方法
服務端渲染執行的就是這個方法,需要SEO的頁面數據的初始化請求放在這個方法里。
需要注意的是,asyncData方法只能在頁面組件里執行,在布局組件跟子組件里加是沒有用的,子組件的數據也想要SEO就只能通過props傳遞過去。
這么寫:
export default { asyncData ({ params }) { return axios.get(`https://my-api/posts/${params.id}`) .then((res) => { return { title: res.data.title } }) } }
咦,怎么頁面的數據不出來? 不要方,加上async 和 await
export default { async asyncData ({ params }) { const { data } = await axios.get(`https://my-api/posts/${params.id}`) return { title: data.title } } }
再看看是不是可以了呢。
這里需要注意,渲染頁面是在asyncData之后才進行的,如果在這寫多個接口請求沒有做優化,那么頁面的白屏時間將會很長~
為了提高渲染效率,那么我們必須要縮短這個請求的時間,這里需要后台同學配合將多個接口聚合成一個接口,我們也可以用 axios.all(promise.all同理) 並發請求。 千萬要記得寫 catch 捕獲異常。
這里還有一段話需要注意:
asyncData is called every time before loading the page component and is only available for such. It will be called server-side once (on the first request to the Nuxt app) and client-side when navigating to further routes. This method receives the context object as the first argument, you can use it to fetch some data and return the component data.
Plugins
官網是這么說的:
Nuxt.js允許您在運行Vue.js應用程序之前執行js插件。這在您需要使用自己的庫或第三方模塊時特別有用。
意思就是在這寫完了某個函數或屬性值,注入到Vue實例(客戶端),context(服務器端)甚至 store(Vuex)。就可以在這些地方使用你寫的函數或屬性值。
例如加個通知信息插件(自定義全局組件同理)
安裝完插件后,在 plugins 目錄中增加文件 vue-notifications.js:
import Vue from 'vue' import VueNotifications from 'vue-notifications' Vue.use(VueNotifications)
在 nuxt.config.js 中需要配置plugins
export default { plugins: [ { src: '~/plugins/vue-notifications', ssr: false } ] }
不支持ssr的系統插件只在瀏覽器使用,可以在插件后面加上 ssr:false 設置
個人觀點,不建議在這引入大量的插件,不然等會打包出來的體積挺大
我在項目中主要用來注冊全局filters跟全局組件,其他頁面的插件盡量做到哪里用哪里引。
在組件中使用引入插件報錯:Window 或 Document 對象未定義?
在組件中使用日期的插件時看到這個問題,猜測:是不是組件腳本在服務端(node層)被執行了?去掉就正常
官方文檔-》常見問題中可以找到了解決的方法。
改前:
import * as lang from "vuejs-datepicker/src/locale/index.js"; import Datepicker from "vuejs-datepicker";
改后:
let lang = null, Datepicker = null; if (process.client) { lang = require("vuejs-datepicker/src/locale/index.js")["zh"];//只引入中文的語言包 Datepicker = require("vuejs-datepicker/dist/vuejs-datepicker.js") }
css預處理器
我用的是less語法,為了方便,不用每次都引入,所以全局注入變量 和 mixin 就不用每次都去導入了,可以使用 @nuxtjs/style-resources 來實現。
安裝
npm i @nuxtjs/style-resources --save--dev
配置
nuxt.config.js :
module.exports = {
modules: [
"@nuxtjs/style-resources" ], styleResources: { less: [ "./assets/scss/variable.less" // 務必使用相對路徑,這里不能用@跟~ ] }, }
權限驗證
官網在路由中有一個中間件的概念,給出的定義是這樣的:
中間件允許您定義一個自定義函數運行在一個頁面或一組頁面渲染之前
怎么這定義看起來很像導航守衛
沒錯,他們的作用是相似的,不同的是這個中間件它 既在服務端調用也在客戶端調用。也可以在某個頁面單獨調用中間件
1.nuxt.config.js全局配置:
export default { ... router: { middleware: 'stats' } } 2.pages/index.vue在頁面中單獨配置: export default { middleware: ['auth', 'stats'] }
你可以在項目中做以下嘗試來理解這句話。
在項目的 middleware/auth.js 中 打印你請求頭里的user-agent
export default function (context) { context.userAgent = process.server ? context.req.headers : navigator.userAgent; console.log(context.userAgent) }
會看到:
- 首次進入頁面 / 刷新頁面 / 跳轉到新標簽頁 時, userAgent 會在命令台打印
-當前窗口跳轉路由 時, userAgent 會在瀏覽器的控制台打印。
既然如此,我們可以在這做權限驗證、路由攔截這些事
Token存放
PC端的網站一般都是多頁面,不是spa模式,因此不考慮使用Vuex進行存取了。
要做到多個頁面同步數據, 可以考慮存放在 localstorage 中,瀏覽器也有提供相關的api進行監聽,具體可參照MDN文檔
window.addEventListener("storage", function(e) { });
需要注意的地方:
-
需要服務器,靜態頁面測試無效果
-
必須是同源(想跨域得使用postMessage,但是會有安全問題)
-
頁面只能收到別的頁面發送的通知
要想省事,就需要的時候再去請求接口拿token,什么煩惱都沒有~
打包優化
打包完vendors.app.js超過4M,太太大了吧。 我來看看是哪個小老弟在搞事情, 把analyze開起來。
nuxt.config.js
export defualt{ build: { analyze: true, // 開啟打包分析 } }
npm run build 一下,打完包會自動在瀏覽器打開頁面
問題很明顯了,開始優化: aliyun的上傳SKD占大頭,動態加載,其他插件哪里用到哪里加。
統統改一遍后:
再把gzip一開, 妥了妥了
如果項目中用到UI框架的, 建議 按需加載 或者走 CDN
首屏效果如何呢
來看看 performance 的結果
渲染速度跟原來后台渲染的基本沒有差距,沒有長時間的白屏等待.
未完待續
-
異常捕獲,錯誤上報,監控這一方面
-
如何在項目中加上typescript(這也是一個坑,差點就出不來了)
-
做一點簡單的自動化測試
-
...
總結
Nuxt.js的英文文檔確實會比中文的多一些信息,中文的有些地方翻譯奇怪,語意不通,還是看他的英文文檔好一點。
框架屬於 “約定大於配置”,如果先用vue-server-renderer 插件搭一遍,再用這個框架會更透徹一些。
從學習到小范圍測試,再到大型項目上線,快一年了,有些坑踩着踩着就忘了,哪天想起來會回來繼續補充。
歡迎大家留言交流~