Vue服務器端渲染nuxt.js初探
開頭還是來一段廢話: 年關將近,給大家拜個早年,愿大家年會都能抽大獎,來年行大運。
廢話不多說,直接進正文。
項目環(huán)境:
前端vue項目, 需要將新增的幾個路由頁面做seo處理。
在調研 插件 prerender-spa-plugin后,發(fā)現無法滿足 vuex 以及 plugins 等要求時,果斷選用了 nuxt.js做服務器渲染。
下面是在項目中整理的 文檔 和 問題
nuxt.js 是一個基于 Vue.js 的通用應用框架
它預設了利用 Vue.js 開發(fā) 服務端渲染(SSR, Server Side Render) 的應用所需要的各種配置,同時也可以一鍵生成靜態(tài)站點。
值得一提的是,nuxt是基于node.js的,后端如果是其他語言時,是否考慮到再加一層node.js的合理性。
鏈接地址: https://zh.nuxtjs.org/guide/installation
利用npx腳手架創(chuàng)建項目
鏈接地址: https://zh.nuxtjs.org/guide/installation
會提供以下選項
1. 在集成的服務器端框架之間進行選擇: Express / Koa ...
2. 選擇您喜歡的UI框架: Bootstrap / Element UI ...
3. 選擇你想要的Nuxt模式 (Universal or SPA) 普通類型 / 單頁應用
4. 添加 axios module 以輕松地將HTTP請求發(fā)送到您的應用程序中。
5. 添加 EsLint 以在保存時代碼規(guī)范和錯誤檢查您的代碼。
6. 添加 Prettier 以在保存時格式化/美化您的代碼。
注意:
1. 如果項目自帶分支等git信息時, 需要將npx生產的目錄里面隱藏的git 文件刪除
因為npx生成文件時,默認為master 分支,類似于 gitmodule 子分支性質
2. 其中第3點,選擇 Universal 時 才會默認輸出靜態(tài)頁,也就是能夠seo的,當選擇spa時,則無法seo
可修改 nuxt.config.js 中的配置項 mode: 'Universal' 來定義類型
啟動項目
命令: npm run dev 默認命令
這時會報錯,說未指定ip 什么的,需配置項:
nuxt.config.js 中:
- server: {
- // port: '3000', // 定義 輸出端口 ,默認為3000
- host:'0.0.0.0' // 定義 輸出 ip
- },
注意:
在server 目錄中國的index.js中 會讀取 nuxt.config.js 中的配置項,當不存在時會賦值默認值
- const {
- host = process.env.HOST || '127.0.0.1',
- port = process.env.PORT || 3000 // 默認配置條件下,修改此處無效 仍舊為3000端口
- } = nuxt.options.server
頁面上的注意點有:
css 都默認加載到 頁面上了;
處理方式有2種:
1. 在 nuxt.config.js 文件 header 配置 link 外鏈這些公共樣式 (下面有具體說明)
2. 在 nuxt.config.js 文件 build 配置 中 自定義文件路徑 以及hash值 (下面有具體說明)
項目目錄結構
1. 資源目錄 (assets)
用于組織未編譯的靜態(tài)資源如 LESS、SASS 或 JavaScript。
2. 組件目錄 (components)
用于組織應用的 Vue.js 組件。Nuxt.js 不會擴展增強該目錄下 Vue.js 組件,
即這些組件不會像頁面組件那樣有 asyncData 方法的特性。
3. 布局目錄 (layouts) 該目錄名為Nuxt.js保留的,不可更改。
用于組織應用的布局組件。
4. 中間件目錄 (middleware)
目錄用于存放應用的中間件
文件名的名稱將成為中間件名稱(middleware/auth.js將成為 auth 中間件)。
一個中間件接收 context 作為第一個參數:
具體參考: https://zh.nuxtjs.org/guide/routing#中間件
5. 頁面目錄 (page) 該目錄名為Nuxt.js保留的,不可更改。
用于組織應用的路由及視圖。Nuxt.js 框架讀取該目錄下所有的 .vue 文件并自動生成對應的路由配置。
nuxt 會根據文件夾名稱以及目錄結構動態(tài)生產 router, 無需額外配置。
6. 靜態(tài)文件目錄 (static)
用于存放應用的靜態(tài)文件,此類文件不會被 Nuxt.js 調用 Webpack 進行構建編譯處理。
服務器啟動的時候,該目錄下的文件會映射至應用的根路徑 / 下。
一般用于 放置公共css,以及 js 文件, 但是如果不想這些css和js走根目錄的話,
需要將這些css放置到 assets中,然后在 nuxt.config.js中 配置 build 選項 下面會具體說明
7. Store 目錄
用于組織應用的 Vuex 狀態(tài)樹 文件
注意: 普通的spa 項目中拋出一個實例對象即可, store為:
- export default new Vuex.Store({
- actions,
- getters,
- })
這里則需要拋出一個 實例函數對象:
- const store = () => {
- return new Vuex.Store({
- state,
- getters,
- mutations,
- actions
- })
- }
- export default store
8. nuxt.config.js
用于組織Nuxt.js 應用的個性化配置,以便覆蓋默認配置。
9. package.json
省略...
別名
~ 或 @ // src目錄
~~ 或 @@ // 根目錄
默認情況下,src目錄和根目錄相同
頁面間路由的跳轉
要在頁面之間使用路由,建議使用
js 中仍然可以使用 $router.push 等方法。
路由跳轉時的頁面間過渡效果
Nuxt.js 默認使用的過渡效果名稱為 page。
需要在 assets/目錄下創(chuàng)建 main.css 添加全局樣式。
- .page-enter-active, .page-leave-active {
- transition: opacity .5s;
- }
- .page-enter, .page-leave-active {
- opacity: 0;
- }
然后添加到 nuxt.config.js 文件中:
- module.exports = {
- css: [
- 'assets/main.css'
- ],
- loading: { color: '#2152F3' },
- }
更多過渡效果: https://zh.nuxtjs.org/guide/routing#過渡動效
頭部信息 (Meta 標簽 ,全局樣式)
nuxt.config.js 里定義應用所需的所有默認 meta 標簽
- head: {
- meta: [
- { charset: 'utf-8' },
- { name: 'viewport', content: 'width=device-width, initial-scale=1' }
- { hid: 'description', name: 'description', content: '' }
- ],
- link: [ // 這里可以引用全局的樣式,但是會默認走根目錄
- { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Roboto' }
- { rel: 'stylesheet', href: '~/static/common.js' } // 文件一般都放在static目錄下
- ]
- }
具體參考:https://zh.nuxtjs.org/api/configuration-head
異步數據 (asyncData方法,限于page頁面組件,components中不適用)
這里包括 asyncData鉤子 / fetch 鉤子 / 。。。
【fetch】 用于在渲染頁面前填充應用的狀態(tài)樹(store)數據, 與 asyncData 方法類似,不同的是它不會設置組件的數據
【asyncData】 主要用于請求ajax 填充data中的數據
每次加載之前被調用。它可以在服務端或路由更新之前被調用。
- asyncData ({ params }) {
- return axios.get(`https://my-api/posts/${params.id}`)
- .then((res) => {
- // 賦值給頁面 data中的數據
- return { title: res.data.title }
- })
- }
或者變換為同步請求:
- async asyncData() {
- let formData = {}
- let ajaxData = await axios({
- method: "post",
- url: url,
- data: qs.stringify(formData),
- retryDelay : 1000,
- withCredentials : true,
- responseType : 'json',
- timeout : 60000,
- 'Content-Type' : 'application/x-www-form-urlencoded'
- })
- }
注意添加 catch
注意:
這個異步請求函數, 第一次執(zhí)行環(huán)境為node環(huán)境中,也就是服務器端,后續(xù)刷新頁面則執(zhí)行環(huán)境為client 客戶端
本地開發(fā)時,如果在客戶端直接請求完整路徑時會經常遇到跨域問題,所以需要在 asyncData 中區(qū)分環(huán)境變量
process.env.VUE_ENV 區(qū)分 是server 還是 client
然后根據不同的環(huán)境配置不同的 url , 并且在 client時, 需要做服務器端代理請求,需要給url增加一層代理標識
例如:client環(huán)境中
- url = '/api' + '/get-user-info';
- nuxt.config.js 中
- /*
- ** 處理代理跨域問題
- */
- axios: {
- proxy: true,
- prefix: '/api', // 增加請求標識
- credentials: true,
- },
- proxy: {
- '/api': {
- // 代理地址
- target: (process.env.NODE_ENV == 'production') ?'http://test.' : 'http://www.' ,
- changeOrigin: true,
- pathRewrite: {
- '^/api': '' // 將標識 替換為 ‘’
- },
- },
- }
錯誤處理 :
context中提供了一個 error(params) 方法,你可以通過調用該方法來顯示錯誤信息頁面。
params.statusCode 可用于指定服務端返回的請求狀態(tài)碼。
- asyncData ({ params, error }) {
- return axios.get(`https://my-api/posts/${params.id}`)
- .then((res) => {
- return { title: res.data.title }
- })
- .catch((e) => {
- error({ statusCode: 404, message: 'Post not found' })
- })
- }
第三方插件的使用
例如:element-ui
需要在 plugins/ 中 添加 element-ui.js
- import Vue from 'vue'
- import Element from 'element-ui/lib/element-ui.common'
- import locale from 'element-ui/lib/locale/lang/en'
- export default () => {
- Vue.use(Element, { locale })
- }
在 uuxt.config.js 中
- plugins: [
- "~/plugins/element-ui",
- // {src : '~/plugins/ga.js' , ssr : false} 是否做ssr處理, false時為在客戶端才加載
- ],
這樣全局就可以使用了
注意:
在使用第三方插件時需要注意 插件內部很多地方都會用到window對象,在服務端會報錯,所以需要將ssr設置為false
在生產環(huán)境中, 有一些插件,在多個頁面中引用,這樣會造成多次加載打包的現象
所以: 在 build配置項中增加配置
- build: {
- vendor:['axios', 'qs'], // 防止多次打包
- }
page 函數鉤子生命周期 以及window 對象
經常會在 第三方組件或者調用的時候 遇到window對象報錯問題。
- asyncData() {
- console.log(window) // 服務端報錯
- console.log(this) // undefined
- },
- fetch() {
- console.log(window) // 服務端報錯
- },
- created () {
- console.log(window) // undefined
- },
- mounted () {
- console.log(window) // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
- }
css js 文件打包文件夾處理
在 uuxt.config.js 中,只需配置生產環(huán)境中。
- build: {
- extractCSS: { allChunks: true }, // css 獨立打包 link 的形式加載
- publicPath: '/sample/assets/', //sample/essays 打包的默認路徑為 ‘_nuxt’ 或者可以指定cdn 域名
- filenames:{ // css 和 js img 打包時指定文件夾
- app: ({ isDev }) => isDev ? '[name].js' : '[chunkhash].js',
- chunk: ({ isDev }) => isDev ? '[name].js' : '[chunkhash].js',
- css: ({ isDev }) => isDev ? '[name].js' : '[contenthash].css',
- img: ({ isDev }) => isDev ? '[path][name].[ext]' : '[hash:7].[ext]'
- }
- },
輸出 css link 路徑: /sample/essays/[contenthash].css
注意: 靜態(tài)資源文件路徑名 不能和頁面路由名稱相同 publicPath 默認配置 '/' 無效
部署
先 npm run build 打包 client文件和 server 文件。
然后 npm runb start 啟動服務器。
pm2 管理:
- pm2 start npm --name "my-nuxt" -- run start
部署時 需要注意 如果是 從其他地方重定向 到 nuxt 環(huán)境中的頁面是, 需要額外配置一個 css / js 重定向路由,并且需要注意 header頭部信息,防止出現 css 文件返回頭部信息為 Content-Type text/plain。
目前項目中只運用到這么多,后續(xù)項目遷移時遇到更多的問題會做補充,如果大家遇到過其他的坑點可以在下面評論中總結出來以及解決方案。