nuxt.js實現服務端渲染ssr (一)(環境配置、 多環境開發、程式守護、服務端映象)

CalvinXie發表於2020-02-16

nuxt.js是一個基於 Vue.js 的輕量級應用框架,可用來建立服務端渲染 (SSR) 應用,也可充當靜態站點引擎生成靜態站點應用——nuxt官網

根據自己在這上方面一條路走到黑,摸滾帶爬的踩坑經驗,接下來我將從以下幾個方面給各位接觸到或者未來接觸到這方面知識的碼友進行全方位的講解和剖析。

  1. 背景
  2. nuxt框架的概述
  3. 百度蜘蛛爬蟲的機制
  4. nuxt初探(開發環境搭建)
  5. 深入nuxt ssr(服務端渲染)
  6. 多環境配置 (開發、測試、生產)
  7. Nginx反向代理
  8. pm2守護node程式配置
  9. 服務端docker容器部署前端工程
  10. 總結
背景

最近因公司業務需求,需要對商城官網做seo優化。因為第一版前端用的是純vue寫的,經Vue-cli整合的webpack打包還有程式碼壓縮處理後生成一串js的靜態檔案。因百度蜘蛛無法對純js的網頁進行爬取收錄。所以不利於網站的排名。緊接我們和領導開會拍板使用服務端渲染(以下用ssr指代),nuxt剛好是vue團隊打造的基於vue的ssr的框架,簡單易於上手,避免了前端攻城獅們自己用node.js搭建服務。但初始我們採用的是nuxt的靜態化部署,nuxt generate ,這樣可以解決一部分需求,但每次更新內容後都要前端手動執行命令打包。而且通過百度蜘蛛爬取趨勢圖顯示這種方式並不理想。最後,我們還是在原來的基礎上採用nuxt的第二種方式前端做ssr處理,上線後經百度蜘蛛爬取趨勢圖顯示有很大程度的提高,利於seo,且官網被百度收錄的頁面也逐漸增加。由此告一段落。

nuxt框架的概述

nuxt作為一個框架,則整合了vue2、vue-route、vuex、vue ssr、vue-meta等元件/框架,其是用webpack、和vue-loader、babel-loader來處理程式碼的自動化構建工作(打包、程式碼分層、壓縮等) nuxt框架提供了兩種部署方式: 1. 靜態化部署(預渲染)-- 通過 nuxt generate 命令實現。該命令依據應用的路由配置將每一個路由靜態化成為對應的 HTML 檔案 2. ssr部署 -- 先通過nuxt build編譯構建再通過nuxt start開啟一個web服務 在服務端調取介面時,主要是用到了asyncData/fetch方法。使得我們可以在設定元件的資料之前能非同步獲取或處理資料。

asyncData方法會在元件(限於頁面元件)每次載入之前被呼叫。它可以在服務端或路由更新之前被呼叫。 在這個方法被呼叫的時候,第一個引數被設定為當前頁面的上下文物件,你可以利用 asyncData方法來獲取資料,Nuxt.js 會將 asyncData 返回的資料融合元件 data 方法返回的資料一併返回給當前元件。

注意:由於asyncData方法是在元件 初始化 前被呼叫的,所以在方法內是沒有辦法通過 this 來引用元件的例項物件。

fetch 方法用於在渲染頁面前填充應用的狀態樹(store)資料, 與 asyncData 方法類似,不同的是它不會設定元件的資料。 型別: Function 如果頁面元件設定了 fetch 方法,它會在元件每次載入前被呼叫(在服務端或切換至目標路由之前)。fetch 方法的第一個引數是頁面元件的上下文物件 context,我們可以用 fetch 方法來獲取資料填充應用的狀態樹。為了讓獲取過程可以非同步,你需要返回一個 Promise,Nuxt.js 會等這個 promise 完成後再渲染元件。

警告: 您無法在內部使用this獲取元件例項,fetch是在元件初始化之前被呼叫

大概瞭解了這些知識就可以做ssr工作了,路由 環境配置等可到中文官網https://zh.nuxtjs.org 查閱

百度蜘蛛爬蟲的機制

百度蜘蛛是百度搜尋引擎的一個自動化程式,它會不斷的訪問收集網際網路上的網頁、文章、視訊等,通過抓取連結來收錄網站,計算網站的權重和排名。純html等靜態化網站對百度蜘蛛比較友好,且百度蜘蛛幾乎不會爬取js動態的網站,如vue/react構建的且經webpack/gulp等構建工具壓縮處理過的網站。百度蜘蛛爬取網站是從主站開始爬,一次根據網站暴露的內鏈依次往深層次爬取。meta的設定,以及網站TDK的優化,網站結構優化,外鏈,文章原創等同樣對SEO有很大作用,但本文主要是從技術層面入手,則主要是針對網站內鏈的處理以及基於vue等現在技術流做ssr處理。

nuxt初探(開發環境搭建)

安裝

我這裡採用nuxt.js團隊建立的腳手架creat-nuxt-app搭建。 注: 開發必備環境 npx (npm v5.2.0+) node (v4.0+) 我的開發環境npm v5.5.1 node v8.9.1 win10 在終端輸入以下命令

npx create-nuxt-app nuxt-demo
複製程式碼

然後你會看到

nuxt-1.png
nuxt-2.png
當執行命令

npm run dev
複製程式碼

當你看到如下圖恭喜你成功搭建起專案

nuxt-3.png
當你和後端互動時,為了解決跨域問題這邊需要通過proxy利用本地node伺服器做個反向代理 在nuxt.config.js中配置

module.exports= {
	modules: [
		'@nuxtjs/axios'
	],
	axios: {
		proxy: true, //開啟代理
		credentials: true, //跨域請求需使用憑證
	},
	proxy: [
		['/api',{ 
			target: 'http://example.com/api', // (後端請求地址)
			changeOrigin: true,
			pathRewrite: {'^/api': ''}
		}]
	]
}

複製程式碼

附上剛搭建完的nuxt.config.js和package.json

// nuxt.config.js

module.exports = {
  mode: 'universal',
  /*
  ** Headers of the page
  */
  head: {
    title: process.env.npm_package_name || '',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },
  /*
  ** Customize the progress-bar color
  */
  loading: { color: '#fff' },
  /*
  ** Global CSS
  */
  css: [
    'element-ui/lib/theme-chalk/index.css'
  ],
  /*
  ** Plugins to load before mounting the App
  */
  plugins: [
    '@/plugins/element-ui'
  ],
  /*
  ** Nuxt.js dev-modules
  */
  buildModules: [
    // Doc: https://github.com/nuxt-community/eslint-module
    '@nuxtjs/eslint-module',
  ],
  /*
  ** Nuxt.js modules
  */
  modules: [
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
    '@nuxtjs/pwa',
    // Doc: https://github.com/nuxt-community/dotenv-module
    '@nuxtjs/dotenv',
  ],
  /*
  ** Axios module configuration
  ** See https://axios.nuxtjs.org/options
  */
  axios: {
  },
  /*
  ** Build configuration
  */
  build: {
    transpile: [/^element-ui/],
    /*
    ** You can extend webpack config here
    */
    extend (config, ctx) {
    }
  }
}

複製程式碼
//package.json
{
  "name": "nuxt-demo",
  "version": "1.0.0",
  "description": "for a nuxt demo about ssr",
  "author": "kevin xie",
  "private": true,
  "scripts": {
    "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
    "build": "nuxt build",
    "start": "cross-env NODE_ENV=production node server/index.js",
    "generate": "nuxt generate",
    "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
    "test": "jest"
  },
  "dependencies": {
    "nuxt": "^2.0.0",
    "cross-env": "^5.2.0",
    "express": "^4.16.4",
    "element-ui": "^2.4.11",
    "@nuxtjs/axios": "^5.3.6",
    "@nuxtjs/pwa": "^3.0.0-0",
    "@nuxtjs/dotenv": "^1.4.0"
  },
  "devDependencies": {
    "nodemon": "^1.18.9",
    "@nuxtjs/eslint-config": "^2.0.0",
    "@nuxtjs/eslint-module": "^1.0.0",
    "babel-eslint": "^10.0.1",
    "eslint": "^6.1.0",
    "eslint-plugin-nuxt": ">=0.4.2",
    "@vue/test-utils": "^1.0.0-beta.27",
    "babel-jest": "^24.1.0",
    "jest": "^24.1.0",
    "vue-jest": "^4.0.0-0"
  }
}

複製程式碼

深入nuxt ssr(服務端渲染)

如果你不需要做ssr處理的話直接執行nuxt generate則nuxt會為你生成靜態化頁面 ,然後再執行nuxt build 打包後將dist資料夾推上伺服器就可以上線了,但這種seo不太友好但比之前vue構建好一點。 但要做ssr處理的話就需換種方式處理也就是nuxt提供的第二種方式,在伺服器搭個node伺服器然後直接在上面跑。這樣的話,配置需要改,且與後臺的請求也需要改,改為nuxt給我們提供asyncData/fetch。因為nuxt也是基於vue開發的,所以生命週期也一樣,但nuxt在服務端中會觸發beforeCreate 、created兩個生命週期。其次,nuxt有自己一套伺服器渲染流程。

nuxt-4.PNG

Nuxt.js 提供了幾種不同的方法來使用 asyncData 方法,你可以選擇自己熟悉的一種來用:

  1. 返回一個 Promise, nuxt.js會等待該Promise被解析之後才會設定元件的資料,從而渲染元件.
  2. 使用 async 或 await
  3. 使用回撥函式
返回Promise
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 }
  }
}
複製程式碼
使用回撥函式
export default {
  asyncData ({ params }, callback) {
    axios.get(`https://my-api/posts/${params.id}`)
      .then((res) => {
        callback(null, { title: res.data.title })
      })
  }
}
複製程式碼

另外,還要注意一下在做登入處理等需要獲取cookie或者服務端儲存的session時,需要使用到vuex狀態樹,在狀態樹中有個方法非常有用 nuxtServerInit

actions: {
  nuxtServerInit ({ commit }, { req }) {
    if (req.session.user) {
      commit('user', req.session.user)
    }
  }
}
複製程式碼

多環境配置 (開發、測試、生產)

因為在一個工程專案中我們都是有開發、測試、生產這樣的驗收流程的。而往往我們測試和生產所連線的資料庫都是不同的,且請求域也不一樣。因此為了開發方便我們就很需要多環境的配置。在spa中我們可以在package.json中通過cross-env這個node環境變數設定,但在ssr中這個在服務端請求時不生效,這裡我當時找了很久google之類最後還是通過 @nuxtjs/dotenv解決了。還有特別注意到 @nuxtjs/axios中有提供環境變數API_URL來複寫baseURL和API_URL_BROWSER來複寫browserBaseURL,這在多環境配置中很有用。 你可以在nuxt.config.js中引入並且在module中使用。

require('dotenv').config({path: '.env'})
module.exports={
	modules: [
		'@nuxtjs/axios',
		'@nuxtjs/dotenv'
	]
}
複製程式碼

同時你可以在根目錄下配置相關環境配置檔案。

.env.dev

NODE_ENV=development
API_URL_BROWSER=http://localhost:3000
API_URL=http://localhost:3000/api
複製程式碼

.env.test

NODE_ENV=test
API_URL_BROWSER=http://test.example.com
API_URL=http://test.example.com/api
複製程式碼

.env.prod

NODE_ENV=production
API_URL_BROWSER=https://www.example.com
API_URL=https://www.example.com/api
複製程式碼

當你用axios做跨域請求時,你就可以自動根據環境給axios配置baseURL。

import axios from 'axios';
const axiosInstance = axios.create({
	timeout: 10000,
	baseURL: process.env.API_URL
})
複製程式碼

在某些需要跳轉的連結nuxt-link或a連結中你可以宣告一個變數對域名做統一處理。

export default {
	data () {
		return {
			BASE_PATH: process.env.API_URL_BROWSER
		}
	}
}
複製程式碼

Nginx反向代理

在生成環境中,我需要使用到nginx做代理伺服器,解決跨域。因為我們的專案的前後端可能部署在不同的服務上。

upstream nodenuxt {
    server 127.0.0.1:3000; #nuxt專案 監聽埠
    keepalive 64;
}
server {
    listen 80;
    server_name https://www.example.com; #訪問域名  
    location / {
        proxy_http_version 1.1;        
        proxy_set_header Upgrade $http_upgrade;  
        proxy_set_header Connection "upgrade";        
        proxy_set_header Host $host;        
        proxy_set_header X-Nginx-Proxy true;        
        proxy_cache_bypass $http_upgrade;        
        proxy_pass http://nodenuxt; #反向代理
    }
}
複製程式碼

pm2守護node程式配置

當我們配置以上內容後在服務端執行npm start專案可以跑起來,但我們的專案總會掛掉的,為了使我們服務程式常駐,我們要用到pm2來守護node程式。並且可以用它來做自動重啟、效能監控以及負載均衡。 你可以全域性安裝npm install pm2 -g 其他麻油說 執行命令可以成功,但我這邊還是沒成功pm2 start npm --name "nuxt-demo" -- run start //(nuxt-demo為你的package.json中的專案名) 我們知道package.json這個檔案,當我們執行npm run dev的時候,其實使用npm去啟動了./node_modules/nuxt/bin/nuxt這個檔案。當我們cd到我們的專案目錄之後,我們最終可以執行如下命令來啟動:

pm2 start ./node_modules/nuxt/bin/nuxt.js -- start
複製程式碼

這樣可以將專案跑起來,但當專案報錯時我們無法看到日誌,因此我這裡在根目錄通過配置pm2.config.js。

module.exports = {
	apps: [
		{
			name: 'nuxt-demo',//專案名稱
			cwd: './',//當前工作路徑
			script: 'npm',//實際啟動指令碼
			args: 'run start',//引數
			autorestart: true, //自動重啟
			error_file: 'logs/nuxt-demo-err.log',//錯誤日誌
			out_file: 'logs/nuxt-demo-out.log', //正常執行日誌
			exec_mode: 'cluster_mode',// 應用啟動模式,支援fork和cluster模式
			min_uptime: '60s', //應用執行少於時間被認為是異常啟動
			restart_delay: '60s',//重啟時延
			instance: 4,//開啟4個例項,僅在cluster模式有效,用於負載均衡
			watch: true,//監控變化的目錄,一旦變化,自動重啟
			watch: ['.nuxt', 'nuxt.config.js'],//監控變化的目錄
			watch_delay: 1000,//監控時延
			ignore_watch: ['node_modules'],//從監控目錄中排除
			watch_options: { // 監聽配置
				'followSymlinks': false,
				'usePolling': true
			}
		}
	]
}
複製程式碼

這裡我是在package.json中引用這個配置檔案的。在伺服器中執行命令就行。(若有報錯可能需要安裝babel轉譯

npm install babel-cli babel-core babel-preset-es2015 --save-dev
複製程式碼
{
	"scripts": {
		"dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server --exec babel-node",
		"build": "nuxt build",
		"start:test": "cross-env NODE_ENV=test node server/index.js pm2 start pm2.config.js --exec babel-node",
		"start:prod": "cross-env NODE_ENV=production node server/index.js pm2 start.config.js --exec babel-node",
	}
}
複製程式碼

服務端docker容器部署前端工程

建立Dockerfile 參考這篇文章

FROM node:alpine

RUN mkdir -p /app/src
COPY ./src /app/src
WORKDIR /app/src

ENV HOST "0.0.0.0"

RUN sed -i "s/dl-cdn.alpinelinux.org/${ALPINE_REPOSITORIES}/g" /etc/apk/repositories

RUN apk add --no-cache make gcc g++ python

RUN npm config set registry https://registry.npm.taobao.org
RUN npm install -g pm2
RUN npm install
RUN npm run build
RUN npm cache clean --force

RUN apk del make gcc g++ python

EXPOSE 3000
CMD ["npm", "run", "start:test"]
#CMD ["npm", "run", "start:prod"]
複製程式碼

然後構建映象

docker build -t nuxt-demo
複製程式碼

啟動容器

docker run -dt -p 3000:3000 nuxt-demo
複製程式碼

注意:這裡服務端部署後需要0.0.0.0:3000訪問,則我們前端還需配置主機號和埠,我這邊在package.json中沒成功,在nuxt.config.js中配置成功了。

module.exports = {
	server: {
		host: '0.0.0.0',
		port: 3000
	}
}
複製程式碼

總結

總得來說,在coding world中要不斷的學習,沒什麼問題解決不了,堅持就對了。若有小夥伴對以上內容有不解歡迎評論,我會盡力為你答疑解惑的。最後喜歡這篇文章的小夥伴關注一下小生並點個贊。 附:

史記·大疫

己亥末,庚子春,荊楚大疫,染者數萬,眾惶恐,舉國防,皆閉戶,道無車舟,萬巷空寂。然外狼亦動,垂涎而候,華夏腹背芒刺。幸龍魂不死,風雨而立。醫無私,警無畏,民齊心!政者,醫者,兵者,扛鼎逆行勇戰矣!商客,名家,百姓,仁義者,鄰邦獻物捐資,嘆山川異域,風月同天,豈曰無衣,與子同裳!能者竭力,萬民同心。月餘,疫除,終勝。此後百年,風調雨順,國泰民安!

相關文章