nuxt.js是一個(gè)基于 Vue.js 的輕量級(jí)應(yīng)用框架,可用來(lái)創(chuàng)建服務(wù)端渲染 (SSR) 應(yīng)用,也可充當(dāng)靜態(tài)站點(diǎn)引擎生成靜態(tài)站點(diǎn)應(yīng)用——nuxt官網(wǎng)
根據(jù)自己在這上方面一條路走到黑局劲,摸滾帶爬的踩坑經(jīng)驗(yàn),接下來(lái)我將從以下幾個(gè)方面給各位接觸到或者未來(lái)接觸到這方面知識(shí)的碼友進(jìn)行全方位的講解和剖析奶赠。
- 背景
- nuxt框架的概述
- 百度蜘蛛爬蟲(chóng)的機(jī)制
- nuxt初探(開(kāi)發(fā)環(huán)境搭建)
- 深入nuxt ssr(服務(wù)端渲染)
- 多環(huán)境配置 (開(kāi)發(fā)鱼填、測(cè)試、生產(chǎn))
- Nginx反向代理
- pm2守護(hù)node進(jìn)程配置
- 服務(wù)端docker容器部署前端工程
- 總結(jié)
背景
最近因公司業(yè)務(wù)需求毅戈,需要對(duì)商城官網(wǎng)做seo優(yōu)化苹丸。因?yàn)榈谝话媲岸擞玫氖羌僾ue寫(xiě)的,經(jīng)Vue-cli集成的webpack打包還有代碼壓縮處理后生成一串js的靜態(tài)文件苇经。因百度蜘蛛無(wú)法對(duì)純js的網(wǎng)頁(yè)進(jìn)行爬取收錄赘理。所以不利于網(wǎng)站的排名。緊接我們和領(lǐng)導(dǎo)開(kāi)會(huì)拍板使用服務(wù)端渲染(以下用ssr指代)扇单,nuxt剛好是vue團(tuán)隊(duì)打造的基于vue的ssr的框架商模,簡(jiǎn)單易于上手,避免了前端攻城獅們自己用node.js搭建服務(wù)蜘澜。但初始我們采用的是nuxt的靜態(tài)化部署施流,nuxt generate ,這樣可以解決一部分需求鄙信,但每次更新內(nèi)容后都要前端手動(dòng)執(zhí)行命令打包瞪醋。而且通過(guò)百度蜘蛛爬取趨勢(shì)圖顯示這種方式并不理想。最后扮碧,我們還是在原來(lái)的基礎(chǔ)上采用nuxt的第二種方式前端做ssr處理趟章,上線(xiàn)后經(jīng)百度蜘蛛爬取趨勢(shì)圖顯示有很大程度的提高杏糙,利于seo,且官網(wǎng)被百度收錄的頁(yè)面也逐漸增加蚓土。由此告一段落宏侍。
nuxt框架的概述
nuxt作為一個(gè)框架,則集成了vue2蜀漆、vue-route谅河、vuex、vue ssr确丢、vue-meta等組件/框架绷耍,其是用webpack、和vue-loader鲜侥、babel-loader來(lái)處理代碼的自動(dòng)化構(gòu)建工作(打包褂始、代碼分層、壓縮等)
nuxt框架提供了兩種部署方式:
1. 靜態(tài)化部署(預(yù)渲染)-- 通過(guò) nuxt generate 命令實(shí)現(xiàn)描函。該命令依據(jù)應(yīng)用的路由配置將每一個(gè)路由靜態(tài)化成為對(duì)應(yīng)的 HTML 文件
2. ssr部署 -- 先通過(guò)nuxt build編譯構(gòu)建再通過(guò)nuxt start開(kāi)啟一個(gè)web服務(wù)
在服務(wù)端調(diào)取接口時(shí)崎苗,主要是用到了asyncData/fetch方法。使得我們可以在設(shè)置組件的數(shù)據(jù)之前能異步獲取或處理數(shù)據(jù)舀寓。
asyncData方法會(huì)在組件(限于頁(yè)面組件)每次加載之前被調(diào)用胆数。它可以在服務(wù)端或路由更新之前被調(diào)用。 在這個(gè)方法被調(diào)用的時(shí)候互墓,第一個(gè)參數(shù)被設(shè)定為當(dāng)前頁(yè)面的上下文對(duì)象必尼,你可以利用 asyncData方法來(lái)獲取數(shù)據(jù),Nuxt.js 會(huì)將 asyncData 返回的數(shù)據(jù)融合組件 data 方法返回的數(shù)據(jù)一并返回給當(dāng)前組件篡撵。
注意:由于asyncData方法是在組件 初始化 前被調(diào)用的判莉,所以在方法內(nèi)是沒(méi)有辦法通過(guò) this 來(lái)引用組件的實(shí)例對(duì)象。
fetch 方法用于在渲染頁(yè)面前填充應(yīng)用的狀態(tài)樹(shù)(store)數(shù)據(jù)酸休, 與 asyncData 方法類(lèi)似骂租,不同的是它不會(huì)設(shè)置組件的數(shù)據(jù)。
類(lèi)型: Function
如果頁(yè)面組件設(shè)置了 fetch 方法斑司,它會(huì)在組件每次加載前被調(diào)用(在服務(wù)端或切換至目標(biāo)路由之前)。fetch 方法的第一個(gè)參數(shù)是頁(yè)面組件的上下文對(duì)象 context但汞,我們可以用 fetch 方法來(lái)獲取數(shù)據(jù)填充應(yīng)用的狀態(tài)樹(shù)宿刮。為了讓獲取過(guò)程可以異步,你需要返回一個(gè) Promise私蕾,Nuxt.js 會(huì)等這個(gè) promise 完成后再渲染組件僵缺。
警告: 您無(wú)法在內(nèi)部使用this獲取組件實(shí)例,fetch是在組件初始化之前被調(diào)用
大概了解了這些知識(shí)就可以做ssr工作了踩叭,路由 環(huán)境配置等可到中文官網(wǎng)https://zh.nuxtjs.org 查閱
百度蜘蛛爬蟲(chóng)的機(jī)制
百度蜘蛛是百度搜索引擎的一個(gè)自動(dòng)化程序磕潮,它會(huì)不斷的訪(fǎng)問(wèn)收集互聯(lián)網(wǎng)上的網(wǎng)頁(yè)翠胰、文章、視頻等自脯,通過(guò)抓取鏈接來(lái)收錄網(wǎng)站之景,計(jì)算網(wǎng)站的權(quán)重和排名。純html等靜態(tài)化網(wǎng)站對(duì)百度蜘蛛比較友好膏潮,且百度蜘蛛幾乎不會(huì)爬取js動(dòng)態(tài)的網(wǎng)站锻狗,如vue/react構(gòu)建的且經(jīng)webpack/gulp等構(gòu)建工具壓縮處理過(guò)的網(wǎng)站。百度蜘蛛爬取網(wǎng)站是從主站開(kāi)始爬焕参,一次根據(jù)網(wǎng)站暴露的內(nèi)鏈依次往深層次爬取轻纪。meta的設(shè)置,以及網(wǎng)站TDK的優(yōu)化叠纷,網(wǎng)站結(jié)構(gòu)優(yōu)化刻帚,外鏈,文章原創(chuàng)等同樣對(duì)SEO有很大作用涩嚣,但本文主要是從技術(shù)層面入手我擂,則主要是針對(duì)網(wǎng)站內(nèi)鏈的處理以及基于vue等現(xiàn)在技術(shù)流做ssr處理。
nuxt初探(開(kāi)發(fā)環(huán)境搭建)
安裝
我這里采用nuxt.js團(tuán)隊(duì)創(chuàng)建的腳手架creat-nuxt-app搭建缓艳。
注: 開(kāi)發(fā)必備環(huán)境 npx (npm v5.2.0+) node (v4.0+)
我的開(kāi)發(fā)環(huán)境npm v5.5.1 node v8.9.1 win10
在終端輸入以下命令
npx create-nuxt-app nuxt-demo
然后你會(huì)看到
當(dāng)執(zhí)行命令
npm run dev
當(dāng)你看到如下圖恭喜你成功搭建起項(xiàng)目
當(dāng)你和后端交互時(shí)校摩,為了解決跨域問(wèn)題這邊需要通過(guò)proxy利用本地node服務(wù)器做個(gè)反向代理
在nuxt.config.js中配置
module.exports= {
modules: [
'@nuxtjs/axios'
],
axios: {
proxy: true, //開(kāi)啟代理
credentials: true, //跨域請(qǐng)求需使用憑證
},
proxy: [
['/api',{
target: 'http://example.com/api', // (后端請(qǐng)求地址)
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(服務(wù)端渲染)
如果你不需要做ssr處理的話(huà)直接執(zhí)行nuxt generate則nuxt會(huì)為你生成靜態(tài)化頁(yè)面 ,然后再執(zhí)行nuxt build 打包后將dist文件夾推上服務(wù)器就可以上線(xiàn)了阶淘,但這種seo不太友好但比之前vue構(gòu)建好一點(diǎn)衙吩。
但要做ssr處理的話(huà)就需換種方式處理也就是nuxt提供的第二種方式,在服務(wù)器搭個(gè)node服務(wù)器然后直接在上面跑溪窒。這樣的話(huà)坤塞,配置需要改,且與后臺(tái)的請(qǐng)求也需要改澈蚌,改為nuxt給我們提供asyncData/fetch摹芙。因?yàn)閚uxt也是基于vue開(kāi)發(fā)的,所以生命周期也一樣宛瞄,但nuxt在服務(wù)端中會(huì)觸發(fā)beforeCreate 浮禾、created兩個(gè)生命周期。其次份汗,nuxt有自己一套服務(wù)器渲染流程盈电。
Nuxt.js 提供了幾種不同的方法來(lái)使用 asyncData 方法,你可以選擇自己熟悉的一種來(lái)用:
- 返回一個(gè) Promise, nuxt.js會(huì)等待該P(yáng)romise被解析之后才會(huì)設(shè)置組件的數(shù)據(jù)杯活,從而渲染組件.
- 使用 async 或 await
- 使用回調(diào)函數(shù)
返回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 }
}
}
使用回調(diào)函數(shù)
export default {
asyncData ({ params }, callback) {
axios.get(`https://my-api/posts/${params.id}`)
.then((res) => {
callback(null, { title: res.data.title })
})
}
}
另外匆帚,還要注意一下在做登錄處理等需要獲取cookie或者服務(wù)端存儲(chǔ)的session時(shí),需要使用到vuex狀態(tài)樹(shù)旁钧,在狀態(tài)樹(shù)中有個(gè)方法非常有用 nuxtServerInit
actions: {
nuxtServerInit ({ commit }, { req }) {
if (req.session.user) {
commit('user', req.session.user)
}
}
}
多環(huán)境配置 (開(kāi)發(fā)吸重、測(cè)試互拾、生產(chǎn))
因?yàn)樵谝粋€(gè)工程項(xiàng)目中我們都是有開(kāi)發(fā)、測(cè)試嚎幸、生產(chǎn)這樣的驗(yàn)收流程的颜矿。而往往我們測(cè)試和生產(chǎn)所連接的數(shù)據(jù)庫(kù)都是不同的,且請(qǐng)求域也不一樣鞭铆。因此為了開(kāi)發(fā)方便我們就很需要多環(huán)境的配置或衡。在spa中我們可以在package.json中通過(guò)cross-env這個(gè)node環(huán)境變量設(shè)置,但在ssr中這個(gè)在服務(wù)端請(qǐng)求時(shí)不生效车遂,這里我當(dāng)時(shí)找了很久google之類(lèi)最后還是通過(guò)@nuxtjs/dotenv解決了封断。還有特別注意到@nuxtjs/axios中有提供環(huán)境變量API_URL來(lái)復(fù)寫(xiě)baseURL和API_URL_BROWSER來(lái)復(fù)寫(xiě)browserBaseURL,這在多環(huán)境配置中很有用。
你可以在nuxt.config.js中引入并且在module中使用舶担。
require('dotenv').config({path: '.env'})
module.exports={
modules: [
'@nuxtjs/axios',
'@nuxtjs/dotenv'
]
}
同時(shí)你可以在根目錄下配置相關(guān)環(huán)境配置文件坡疼。
.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
當(dāng)你用axios做跨域請(qǐng)求時(shí),你就可以自動(dòng)根據(jù)環(huán)境給axios配置baseURL衣陶。
import axios from 'axios';
const axiosInstance = axios.create({
timeout: 10000,
baseURL: process.env.API_URL
})
在某些需要跳轉(zhuǎn)的鏈接nuxt-link或a鏈接中你可以聲明一個(gè)變量對(duì)域名做統(tǒng)一處理柄瑰。
export default {
data () {
return {
BASE_PATH: process.env.API_URL_BROWSER
}
}
}
Nginx反向代理
在生成環(huán)境中,我需要使用到nginx做代理服務(wù)器剪况,解決跨域教沾。因?yàn)槲覀兊捻?xiàng)目的前后端可能部署在不同的服務(wù)上。
upstream nodenuxt {
server 127.0.0.1:3000; #nuxt項(xiàng)目 監(jiān)聽(tīng)端口
keepalive 64;
}
server {
listen 80;
server_name https://www.example.com; #訪(fǎng)問(wèn)域名
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守護(hù)node進(jìn)程配置
當(dāng)我們配置以上內(nèi)容后在服務(wù)端執(zhí)行npm start項(xiàng)目可以跑起來(lái)译断,但我們的項(xiàng)目總會(huì)掛掉的授翻,為了使我們服務(wù)進(jìn)程常駐,我們要用到pm2來(lái)守護(hù)node進(jìn)程孙咪。并且可以用它來(lái)做自動(dòng)重啟堪唐、性能監(jiān)控以及負(fù)載均衡。
你可以全局安裝npm install pm2 -g
其他麻油說(shuō) 執(zhí)行命令可以成功翎蹈,但我這邊還是沒(méi)成功pm2 start npm --name "nuxt-demo" -- run start //(nuxt-demo為你的package.json中的項(xiàng)目名)
我們知道package.json這個(gè)文件淮菠,當(dāng)我們執(zhí)行npm run dev的時(shí)候,其實(shí)使用npm去啟動(dòng)了./node_modules/nuxt/bin/nuxt這個(gè)文件荤堪。當(dāng)我們cd到我們的項(xiàng)目目錄之后合陵,我們最終可以執(zhí)行如下命令來(lái)啟動(dòng):
pm2 start ./node_modules/nuxt/bin/nuxt.js -- start
這樣可以將項(xiàng)目跑起來(lái),但當(dāng)項(xiàng)目報(bào)錯(cuò)時(shí)我們無(wú)法看到日志逞力,因此我這里在根目錄通過(guò)配置pm2.config.js曙寡。
module.exports = {
apps: [
{
name: 'nuxt-demo',//項(xiàng)目名稱(chēng)
cwd: './',//當(dāng)前工作路徑
#script: 'npm',//實(shí)際啟動(dòng)腳本
script: './node_modules/nuxt/bin/nuxt.js ',//或者直接執(zhí)行這個(gè)腳本
args: 'run start',//參數(shù)
autorestart: true, //自動(dòng)重啟
error_file: 'logs/nuxt-demo-err.log',//錯(cuò)誤日志
out_file: 'logs/nuxt-demo-out.log', //正常運(yùn)行日志
exec_mode: 'cluster',// 應(yīng)用啟動(dòng)模式,支持fork和cluster模式
min_uptime: '60s', //應(yīng)用運(yùn)行少于時(shí)間被認(rèn)為是異常啟動(dòng)
restart_delay: '60s',//重啟時(shí)延
instances: 4,//開(kāi)啟4個(gè)實(shí)例寇荧,僅在cluster模式有效,用于負(fù)載均衡
watch: true,//監(jiān)控變化的目錄执隧,一旦變化揩抡,自動(dòng)重啟
watch: ['.nuxt', 'nuxt.config.js'],//監(jiān)控變化的目錄
watch_delay: 1000,//監(jiān)控時(shí)延
ignore_watch: ['node_modules'],//從監(jiān)控目錄中排除
watch_options: { // 監(jiān)聽(tīng)配置
'followSymlinks': false,
'usePolling': true
}
}
]
}
這里我是在package.json中引用這個(gè)配置文件的户侥。在服務(wù)器中執(zhí)行命令就行。(若有報(bào)錯(cuò)可能需要安裝babel轉(zhuǎn)譯 ```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 pm2.config.js --exec babel-node",
}
}
服務(wù)端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"]
然后構(gòu)建鏡像
docker build -t nuxt-demo
啟動(dòng)容器
docker run -dt -p 3000:3000 nuxt-demo
注意:這里服務(wù)端部署后需要0.0.0.0:3000訪(fǎng)問(wèn)峦嗤,則我們前端還需配置主機(jī)號(hào)和端口蕊唐,我這邊在package.json中沒(méi)成功,在nuxt.config.js中配置成功了烁设。
module.exports = {
server: {
host: '0.0.0.0',
port: 3000
}
}
總結(jié)
總得來(lái)說(shuō)替梨,在coding world中要不斷的學(xué)習(xí),沒(méi)什么問(wèn)題解決不了装黑,堅(jiān)持就對(duì)了副瀑。若有小伙伴對(duì)以上內(nèi)容有不解歡迎評(píng)論,我會(huì)盡力為你答疑解惑的恋谭。最后喜歡這篇文章的小伙伴關(guān)注一下小生并點(diǎn)個(gè)贊糠睡。
附:
史記·大疫
己亥末,庚子春疚颊,荊楚大疫狈孔,染者數(shù)萬(wàn),眾惶恐材义,舉國(guó)防均抽,皆閉戶(hù),道無(wú)車(chē)舟其掂,萬(wàn)巷空寂油挥。然外狼亦動(dòng),垂涎而候清寇,華夏腹背芒刺喘漏。幸龍魂不死,風(fēng)雨而立华烟。醫(yī)無(wú)私翩迈,警無(wú)畏,民齊心盔夜!政者负饲,醫(yī)者,兵者喂链,扛鼎逆行勇戰(zhàn)矣返十!商客,名家椭微,百姓洞坑,仁義者,鄰邦獻(xiàn)物捐資蝇率,嘆山川異域迟杂,風(fēng)月同天刽沾,豈曰無(wú)衣,與子同裳排拷!能者竭力侧漓,萬(wàn)民同心。月余监氢,疫除布蔗,終勝。此后百年浪腐,風(fēng)調(diào)雨順纵揍,國(guó)泰民安!