服務(wù)端渲染(SSR)和瀏覽器端渲染 (CSR)

轉(zhuǎn)載服務(wù)端渲染(SSR) - 知乎 (zhihu.com)

一包竹、什么是瀏覽器端渲染 (CSR)淡喜?

CSR是Client Side Render簡(jiǎn)稱;頁(yè)面上的內(nèi)容是我們加載的js文件渲染出來(lái)的娱挨,js文件運(yùn)行在瀏覽器上面抖苦,服務(wù)端只返回一個(gè)html模板毯侦。


CSR加載圖

二洒缀、什么是服務(wù)器端渲染 (SSR)瑰谜?

SSR是Server Side Render簡(jiǎn)稱;頁(yè)面上的內(nèi)容是通過(guò)服務(wù)端渲染生成的树绩,瀏覽器直接顯示服務(wù)端返回的html就可以了萨脑。


SSR加載圖

本文以Vue.js 做為演示框架來(lái)區(qū)分SSR和CSR。默認(rèn)情況下葱峡,Vue.js可以在瀏覽器中輸出 Vue 組件砚哗,進(jìn)行生成 DOM 和操作 DOM龙助。然而也可以將同一個(gè)組件渲染為服務(wù)器端的 HTML 字符串砰奕,將它們直接發(fā)送到瀏覽器蛛芥,最后將這些靜態(tài)標(biāo)記"激活"為客戶端上完全可交互的應(yīng)用程序。

服務(wù)器渲染的 Vue.js 應(yīng)用程序也可以被認(rèn)為是"同構(gòu)"或"通用"军援,因?yàn)閼?yīng)用程序的大部分代碼都可以在服務(wù)器和客戶端上運(yùn)行仅淑。

附:vue-ssr官方文檔

基本用法 | Vue SSR 指南ssr.vuejs.org/zh/guide/

三、不同渲染方式在瀏覽器解析情況

從輸入頁(yè)面URL到頁(yè)面渲染完成大致流程為:

解析URL

瀏覽器本地緩存

DNS解析

建立TCP/IP連接

發(fā)送HTTP請(qǐng)求

服務(wù)器處理請(qǐng)求并返回HTTP報(bào)文

瀏覽器根據(jù)深度遍歷的方式把html節(jié)點(diǎn)遍歷構(gòu)建DOM樹(shù)

遇到CSS外鏈胸哥,異步加載解析CSS涯竟,構(gòu)建CSS規(guī)則樹(shù)

遇到script標(biāo)簽,如果是普通JS標(biāo)簽則同步加載并執(zhí)行空厌,阻塞頁(yè)面渲染庐船,如果標(biāo)簽上有defer / async屬性則異步加載JS資源

將dom樹(shù)和CSS DOM樹(shù)構(gòu)造成render樹(shù)

渲染render樹(shù)


performance.timing


CSR-瀏覽器performance情況


SSR-瀏覽器performance情況

FP:首次繪制。用于標(biāo)記導(dǎo)航之后瀏覽器在屏幕上渲染像素的時(shí)間點(diǎn)嘲更。這個(gè)不難理解筐钟,就是瀏覽器開(kāi)始請(qǐng)求網(wǎng)頁(yè)到網(wǎng)頁(yè)首幀繪制的時(shí)間點(diǎn)。這個(gè)指標(biāo)表明了網(wǎng)頁(yè)請(qǐng)求是否成功赋朦。

FCP:首次內(nèi)容繪制篓冲。FCP 標(biāo)記的是瀏覽器渲染來(lái)自 DOM 第一位內(nèi)容的時(shí)間點(diǎn),該內(nèi)容可能是文本宠哄、圖像壹将、SVG 甚至?<canvas>?元素。

FMP:首次有效繪制毛嫉。這是一個(gè)很主觀的指標(biāo)诽俯。根據(jù)業(yè)務(wù)的不同,每一個(gè)網(wǎng)站的有效內(nèi)容都是不相同的承粤,有效內(nèi)容就是網(wǎng)頁(yè)中"主角元素"惊畏。對(duì)于視頻網(wǎng)站而言,主角元素就是視頻密任。對(duì)于搜索引擎而言颜启,主角元素就是搜索框。

TTI:可交互時(shí)間浪讳。用于標(biāo)記應(yīng)用已進(jìn)行視覺(jué)渲染并能可靠響應(yīng)用戶輸入的時(shí)間點(diǎn)缰盏。應(yīng)用可能會(huì)因?yàn)槎喾N原因而無(wú)法響應(yīng)用戶輸入:①頁(yè)面組件運(yùn)行所需的JavaScript尚未加載完成。②耗時(shí)較長(zhǎng)的任務(wù)阻塞主線程

根據(jù)上圖devtool時(shí)間軸的結(jié)果淹遵,雖然CSR配合預(yù)渲染方式(loading口猜、骨架圖)可以提前FP、FCP從而減少白屏問(wèn)題透揣,但無(wú)法提前FMP济炎;SSR將FMP提前至js加載前觸發(fā),提前顯示網(wǎng)頁(yè)中的"主角元素"辐真。SSR不僅可以減少白屏?xí)r間還可以大幅減少首屏加載時(shí)間须尚。

附:首屏?xí)r間獲取方法

前端 白屏?xí)r間如何獲妊碌獭?18 贊同 · 9 評(píng)論回答

四耐床、node服務(wù)(server.js)

第一步 利用express框架寫(xiě)一個(gè)簡(jiǎn)單node服務(wù)

Express是基于Node.js平臺(tái)密幔,快速、開(kāi)放撩轰、極簡(jiǎn)的 Web 開(kāi)發(fā)框架

/*第一步 利用express框架寫(xiě)一個(gè)簡(jiǎn)單node服務(wù)*/letexpress=require('express');letapp=express();app.get('*',function(req,res){res.send('hello world');});constport=process.env.PORT||8080app.listen(port,()=>{console.log(`server started at localhost:${port}`)})

附:express文檔

Express - 基于 Node.js 平臺(tái)的 web 應(yīng)用開(kāi)發(fā)框架www.expressjs.com.cn/

第二步 利用vue-server-renderer提供的createRenderer將vue與node結(jié)合

renderer.renderToString(vm, context?, callback?): ?Promise<string>

將 Vue 實(shí)例渲染為字符串胯甩。上下文對(duì)象 (context object) 可選】吧回調(diào)函數(shù)是典型的 Node.js 風(fēng)格回調(diào)偎箫,其中第一個(gè)參數(shù)是可能拋出的錯(cuò)誤,第二個(gè)參數(shù)是渲染完畢的字符串皆串。

/*第一步 利用express框架寫(xiě)一個(gè)簡(jiǎn)單node服務(wù)第二步 利用vue-server-renderer提供的createRenderer將vue與node結(jié)合*/

let express=require('express');

let app=express();

constVue=require('vue')constrenderer=require('vue-server-renderer').createRenderer()app.get('*',function(req,res){render(req,res)});functionrender(req,res){constapp=newVue({data:{url:req.url},template:`<div>req.url:{{ url }}</div>`})renderer.renderToString(app,(err,html)=>{if(err){res.status(500).end('Internal Server Error')return}else{res.end(`${html}`)}})}constport=process.env.PORT||8080app.listen(port,()=>{console.log(`server started at localhost:${port}`)})

第三步 讀入index.template.html文件

創(chuàng)建 renderer 時(shí)提供一個(gè)頁(yè)面模板镜廉。多數(shù)時(shí)候,我們會(huì)將頁(yè)面模板放在特有的文件中愚战,例如index.template.html

<!DOCTYPE html><htmllang="en"><head><title>Hello</title></head><body><!--vue-ssr-outlet--></body></html>

<!--vue-ssr-outlet-->注釋 -- 這里將是應(yīng)用程序 HTML 標(biāo)記注入的地方娇唯。

/*第一步 利用express框架寫(xiě)一個(gè)簡(jiǎn)單node服務(wù)第二步 利用vue-server-renderer提供的createRenderer將vue與node結(jié)合第三步 讀入index.template.html文件*/letexpress=require('express');letapp=express();constVue=require('vue')constpath=require('path')constresolve=file=>path.resolve(__dirname,file)constrenderer=require('vue-server-renderer').createRenderer({template:require('fs').readFileSync(resolve('./src/index.template.html'),'utf-8')})app.get('*',function(req,res){render(req,res)});functionrender(req,res){constapp=newVue({data:{url:req.url},template:`<div>req.url:{{ url }}</div>`})constcontext={title:'ssr測(cè)試',}renderer.renderToString(app,context,(err,html)=>{if(err){res.status(500).end('Internal Server Error')return}else{res.end(`${html}`)}})}constport=process.env.PORT||8080app.listen(port,()=>{console.log(`server started at localhost:${port}`)})

第四步?引入已經(jīng)打包好的vue-ssr-server-bundle.json

vue-server-renderer?提供一個(gè)名為?createBundleRenderer?的 API,用于處理此問(wèn)題寂玲,通過(guò)使用 webpack 的自定義插件塔插,server bundle 將生成為可傳遞到 bundle renderer 的特殊 JSON 文件。所創(chuàng)建的 bundle renderer拓哟,用法和普通 renderer 相同想许,但是 bundle renderer 提供以下優(yōu)點(diǎn):

const renderer = createBundleRenderer(serverBundle, {

? runInNewContext: false, // 推薦,bundle 代碼將與服務(wù)器進(jìn)程在同一個(gè) global 上下文中運(yùn)行

? template, // (可選)頁(yè)面模板

? clientManifest // (可選)客戶端構(gòu)建 manifest

})

內(nèi)置的 source map 支持(在 webpack 配置中使用?devtool: 'source-map')

在開(kāi)發(fā)環(huán)境甚至部署過(guò)程中熱重載(通過(guò)讀取更新后的 bundle断序,然后重新創(chuàng)建 renderer 實(shí)例)

關(guān)鍵 CSS(critical CSS) 注入(在使用?*.vue?文件時(shí)):自動(dòng)內(nèi)聯(lián)在渲染過(guò)程中用到的組件所需的CSS流纹。

使用?clientManifest?進(jìn)行資源注入:自動(dòng)推斷出最佳的預(yù)加載(preload)和預(yù)取(prefetch)指令,以及初始渲染所需的代碼分割 chunk违诗。

/*第一步 利用express框架寫(xiě)一個(gè)簡(jiǎn)單node服務(wù)第二步 利用vue-server-renderer提供的createRenderer將vue與node結(jié)合第三步 讀入index.template.html文件第四步 引入已經(jīng)打包好的vue-ssr-server-bundle.json*/letexpress=require('express');letapp=express();constpath=require('path')constresolve=file=>path.resolve(__dirname,file)consttemplatePath=resolve('./src/index.template.html')constserverBundle=require('./dist/vue-ssr-server-bundle.json')const{createBundleRenderer}=require('vue-server-renderer')letrenderer=createBundleRenderer(serverBundle,{template:require('fs').readFileSync(templatePath,'utf-8'),//clientManifest 客戶端構(gòu)建 manifest 暫不演示})app.get('*',function(req,res){render(req,res)});functionrender(req,res){constcontext={title:'ssr測(cè)試',url:req.url// 傳遞path漱凝,這個(gè)參數(shù)很重要}renderer.renderToString(context,(err,html)=>{if(err){res.status(500).end('Internal Server Error')return}else{res.end(`${html}`)}})}constport=process.env.PORT||8080app.listen(port,()=>{console.log(`server started at localhost:${port}`)})

第五步 將bundle換成webpack實(shí)時(shí)輸入的內(nèi)存的bundle(非生產(chǎn)環(huán)境)

webpack 默認(rèn)使用普通文件系統(tǒng)來(lái)讀取文件并將文件寫(xiě)入磁盤(pán)。但是诸迟,還可以使用不同類型的文件系統(tǒng)(內(nèi)存(memory), webDAV 等)來(lái)更改輸入或輸出行為茸炒。為了實(shí)現(xiàn)這一點(diǎn),可以改變inputFileSystem或outputFileSystem

調(diào)用watch方法會(huì)觸發(fā) webpack 執(zhí)行器阵苇,但之后會(huì)監(jiān)聽(tīng)變更(很像 CLI 命令:webpack --watch)壁公,一旦 webpack 檢測(cè)到文件變更,就會(huì)重新執(zhí)行編譯绅项。該方法返回一個(gè)Watching實(shí)例紊册。

/*第一步 利用express框架寫(xiě)一個(gè)簡(jiǎn)單node服務(wù)第二步 利用vue-server-renderer提供的createRenderer將vue與node結(jié)合第三步 讀入index.template.html文件第四步 引入已經(jīng)打包好的vue-ssr-server-bundle.json第五步 將bundle換成webpack實(shí)時(shí)輸入的內(nèi)存的bundle*/letexpress=require('express');letapp=express();constpath=require('path')constresolve=file=>path.resolve(__dirname,file)consttemplatePath=resolve('./src/index.template.html')//const bundle = require('./dist/vue-ssr-server-bundle.json')constwebpack=require('webpack')constserverConfig=require('./build/webpack.server.config')constMFS=require('memory-fs')constreadFile=(fs,file)=>{try{returnfs.readFileSync(path.join(serverConfig.output.path,file),'utf-8')}catch(e){}}const{createBundleRenderer}=require('vue-server-renderer')letrenderer;app.get('*',function(req,res){render(req,res)});functionrender(req,res){constcontext={title:'ssr測(cè)試',url:req.url// 傳遞path,這個(gè)參數(shù)很重要}renderer.renderToString(context,(err,html)=>{if(err){res.status(500).end('Internal Server Error')return}else{res.end(`${html}`)}})}constserverCompiler=webpack(serverConfig)constmfs=newMFS()serverCompiler.outputFileSystem=mfs//打包至內(nèi)存中serverCompiler.watch({},(err,stats)=>{if(err)throwerrletbundle=JSON.parse(readFile(mfs,'vue-ssr-server-bundle.json'))renderer=createBundleRenderer(bundle,{template:require('fs').readFileSync(templatePath,'utf-8'),})})constport=process.env.PORT||8080app.listen(port,()=>{console.log(`server started at localhost:${port}`)})

附:webpack在Node.js 中的API

Node.js API | webpack 中文網(wǎng)www.webpackjs.com/api/node/

五快耿、剖析構(gòu)建流程

構(gòu)建流程

通用配置(Base Config)

服務(wù)器配置 (Server Config)

服務(wù)器配置囊陡,是用于生成傳遞給?createBundleRenderer?的 server bundle芳绩。它應(yīng)該是這樣的:

constmerge=require('webpack-merge')constnodeExternals=require('webpack-node-externals')constbaseConfig=require('./webpack.base.config.js')constVueSSRServerPlugin=require('vue-server-renderer/server-plugin')module.exports=merge(baseConfig,{// 將 entry 指向應(yīng)用程序的 server entry 文件entry:'/path/to/entry-server.js',// 這允許 webpack 以 Node 適用方式(Node-appropriate fashion)處理動(dòng)態(tài)導(dǎo)入(dynamic import),// 并且還會(huì)在編譯 Vue 組件時(shí)关斜,// 告知 `vue-loader` 輸送面向服務(wù)器代碼(server-oriented code)示括。target:'node',// 對(duì) bundle renderer 提供 source map 支持devtool:'source-map',// 此處告知 server bundle 使用 Node 風(fēng)格導(dǎo)出模塊(Node-style exports)output:{libraryTarget:'commonjs2'},// https://webpack.js.org/configuration/externals/#function// https://github.com/liady/webpack-node-externals// 外置化應(yīng)用程序依賴模塊铺浇×⌒螅可以使服務(wù)器構(gòu)建速度更快,// 并生成較小的 bundle 文件鳍侣。externals:nodeExternals({// 不要外置化 webpack 需要處理的依賴模塊丁稀。// 你可以在這里添加更多的文件類型。例如倚聚,未處理 *.vue 原始文件线衫,// 你還應(yīng)該將修改 `global`(例如 polyfill)的依賴模塊列入白名單whitelist:/\.css$/}),// 這是將服務(wù)器的整個(gè)輸出// 構(gòu)建為單個(gè) JSON 文件的插件。// 默認(rèn)文件名為 `vue-ssr-server-bundle.json`plugins:[newVueSSRServerPlugin()]})

在生成?vue-ssr-server-bundle.json?之后惑折,只需將文件路徑傳遞給?createBundleRenderer:

const{createBundleRenderer}=require('vue-server-renderer')constrenderer=createBundleRenderer('/path/to/vue-ssr-server-bundle.json',{// ……renderer 的其他選項(xiàng)})

客戶端配置 (Client Config)

除了 server bundle 之外授账,我們還可以生成客戶端構(gòu)建清單 (client build manifest)。使用客戶端清單 (client manifest) 和服務(wù)器 bundle(server bundle)惨驶,renderer 現(xiàn)在具有了服務(wù)器和客戶端的構(gòu)建信息白热,因此它可以自動(dòng)推斷和注入資源預(yù)加載 / 數(shù)據(jù)預(yù)取指令(preload / prefetch directive),以及 css 鏈接 / script 標(biāo)簽到所渲染的 HTML粗卜。

好處是雙重的:

在生成的文件名中有哈希時(shí)屋确,可以取代?html-webpack-plugin?來(lái)注入正確的資源 URL。

在通過(guò) webpack 的按需代碼分割特性渲染 bundle 時(shí)续扔,我們可以確保對(duì) chunk 進(jìn)行最優(yōu)化的資源預(yù)加載/數(shù)據(jù)預(yù)取攻臀,并且還可以將所需的異步 chunk 智能地注入為?<script>?標(biāo)簽,以避免客戶端的瀑布式請(qǐng)求 (waterfall request)纱昧,以及改善可交互時(shí)間 (TTI - time-to-interactive)刨啸。

要使用客戶端清單 (client manifest),客戶端配置 (client config) 將如下所示:

constwebpack=require('webpack')constmerge=require('webpack-merge')constbaseConfig=require('./webpack.base.config.js')constVueSSRClientPlugin=require('vue-server-renderer/client-plugin')module.exports=merge(baseConfig,{entry:'/path/to/entry-client.js',plugins:[// 重要信息:這將 webpack 運(yùn)行時(shí)分離到一個(gè)引導(dǎo) chunk 中识脆,// 以便可以在之后正確注入異步 chunk呜投。// 這也為你的 應(yīng)用程序/vendor 代碼提供了更好的緩存。newwebpack.optimize.CommonsChunkPlugin({name:"manifest",minChunks:Infinity}),// 此插件在輸出目錄中// 生成 `vue-ssr-client-manifest.json`存璃。newVueSSRClientPlugin()]})

六仑荐、編寫(xiě)通用代碼

組件生命周期鉤子函數(shù),由于沒(méi)有動(dòng)態(tài)更新纵东,所有的生命周期鉤子函數(shù)中粘招,只有beforeCreate和created會(huì)在服務(wù)器端渲染 (SSR) 過(guò)程中被調(diào)用。這就是說(shuō)任何其他生命周期鉤子函數(shù)中的代碼(例如beforeMount或mounted)偎球,只會(huì)在客戶端執(zhí)行

2.通用代碼不可接受特定平臺(tái)的 API洒扎,因此如果你的代碼中辑甜,直接使用了像window或document,這種僅瀏覽器可用的全局變量袍冷,則會(huì)在 Node.js 中執(zhí)行時(shí)拋出錯(cuò)誤磷醋,反之也是如此(global)

解決方案:

(1)在beforeCreate,created生命周期以及全局的執(zhí)行環(huán)境中調(diào)用特定的api前需要判斷執(zhí)行環(huán)境胡诗;

(2)使用adapter模式邓线,寫(xiě)一套adapter兼容不同環(huán)境的api。

七煌恢、數(shù)據(jù)預(yù)取存儲(chǔ)容器

通用 entry(app.js)

app.js?是我們應(yīng)用程序的「通用 entry」骇陈。在純客戶端應(yīng)用程序中,我們將在此文件中創(chuàng)建根 Vue 實(shí)例瑰抵,并直接掛載到 DOM你雌。但是,對(duì)于服務(wù)器端渲染(SSR)二汛,責(zé)任轉(zhuǎn)移到純客戶端 entry 文件婿崭。app.js?簡(jiǎn)單地使用 export 導(dǎo)出一個(gè)?createApp?函數(shù)

服務(wù)端數(shù)據(jù)預(yù)取 (Server entry)

在entry-server.js中,我們可以通過(guò)路由獲得與router.getMatchedComponents()相匹配的組件肴颊,如果組件暴露出asyncData氓栈,我們就調(diào)用這個(gè)方法。然后我們需要將解析完成的狀態(tài)苫昌,附加到渲染上下文(render context)中颤绕。

// entry-server.jsimport{createApp}from'./app'exportdefaultcontext=>{// 因?yàn)橛锌赡軙?huì)是異步路由鉤子函數(shù)或組件,所以我們將返回一個(gè) Promise祟身,// 以便服務(wù)器能夠等待所有的內(nèi)容在渲染前奥务,// 就已經(jīng)準(zhǔn)備就緒。returnnewPromise((resolve,reject)=>{const{app,router,store}=createApp()// 設(shè)置服務(wù)器端 router 的位置router.push(context.url)// 等到 router 將可能的異步組件和鉤子函數(shù)解析完router.onReady(()=>{constmatchedComponents=router.getMatchedComponents()//當(dāng)前路由匹配到組件if(!matchedComponents.length){returnreject({code:404})}// 等到 router 將可能的異步組件和鉤子函數(shù)解析完// 對(duì)所有匹配的路由組件調(diào)用 `asyncData()`Promise.all(matchedComponents.map(Component=>{if(Component.asyncData){returnComponent.asyncData({store,route:router.currentRoute})}})).then(()=>{// 在所有預(yù)取鉤子(preFetch hook) resolve 后袜硫,// 我們的 store 現(xiàn)在已經(jīng)填充入渲染應(yīng)用程序所需的狀態(tài)氯葬。// 當(dāng)我們將狀態(tài)附加到上下文,// 并且 `template` 選項(xiàng)用于 renderer 時(shí)婉陷,// 狀態(tài)將自動(dòng)序列化為 `window.__INITIAL_STATE__`帚称,并注入 HTML。context.state=store.stateresolve(app)}).catch(reject)},reject)})}

客戶端數(shù)據(jù)預(yù)取 (Client entry)

router.onReady該方法把一個(gè)回調(diào)排隊(duì)秽澳,在路由完成初始導(dǎo)航時(shí)調(diào)用闯睹,這意味著它可以解析所有的異步進(jìn)入鉤子和路由初始化相關(guān)聯(lián)的異步組件。router.beforeResolve在導(dǎo)航被確認(rèn)之前担神,同時(shí)在所有組件內(nèi)守衛(wèi)和異步路由組件被解析之后楼吃,解析守衛(wèi)就被調(diào)用。

router.onReady(()=>{// 添加路由鉤子函數(shù),用于處理 asyncData.// 在初始路由 resolve 后執(zhí)行孩锡,// 以便我們不會(huì)二次預(yù)取(double-fetch)已有的數(shù)據(jù)酷宵。// 使用 `router.beforeResolve()`,以便確保所有異步組件都 resolve躬窜。router.beforeResolve((to,from,next)=>{constmatched=router.getMatchedComponents(to)//當(dāng)前路由匹配的組件數(shù)組 constprevMatched=router.getMatchedComponents(from)// 我們只關(guān)心非預(yù)渲染的組件// 所以我們對(duì)比它們浇垦,找出兩個(gè)匹配列表的差異組件letdiffed=falseconstactivated=matched.filter((c,i)=>{returndiffed||(diffed=(prevMatched[i]!==c))})if(!activated.length){returnnext()}// 這里如果有加載指示器 (loading indicator),就觸發(fā)Promise.all(activated.map(c=>{if(c.asyncData){returnc.asyncData({store,route:to})}})).then(()=>{// 停止加載指示器(loading indicator)next()}).catch(next)})app.$mount('#app')})

同一個(gè)組件不同參數(shù)切換路由時(shí)會(huì)觸發(fā)重用組件內(nèi)部beforeRouteUpdate荣挨,通過(guò)全局mixin路由鉤子來(lái)監(jiān)聽(tīng)調(diào)用asyncData方法拉取數(shù)據(jù)進(jìn)行客戶端渲染

Vue.mixin({

????beforeRouteUpdate (to, from, next) {

????const { asyncData } = this.$options

????????if (asyncData) {

????????????asyncData({

????????????????store: this.$store,

????????????????route: to }).then(next).catch(next)

? ? ? ? ? } else {

????????????next()

????????????}

????}

})

附:完整的導(dǎo)航解析流程

導(dǎo)航被觸發(fā)男韧。

在失活的組件里調(diào)用離開(kāi)守衛(wèi)。

調(diào)用全局的?beforeEach?守衛(wèi)垦沉。

在重用的組件里調(diào)用?beforeRouteUpdate?守衛(wèi) (2.2+)煌抒。

在路由配置里調(diào)用?beforeEnter仍劈。

解析異步路由組件厕倍。

在被激活的組件里調(diào)用?beforeRouteEnter。

調(diào)用全局的?beforeResolve?守衛(wèi) (2.5+)贩疙。

導(dǎo)航被確認(rèn)讹弯。

調(diào)用全局的?afterEach?鉤子。

觸發(fā) DOM 更新这溅。

用創(chuàng)建好的實(shí)例調(diào)用?beforeRouteEnter?守衛(wèi)中傳給?next?的回調(diào)函數(shù)组民。

導(dǎo)航守衛(wèi) | Vue Routerrouter.vuejs.org/zh/guide/advanced/navigation-guards.html#%E5%AE%8C%E6%95%B4%E7%9A%84%E5%AF%BC%E8%88%AA%E8%A7%A3%E6%9E%90%E6%B5%81%E7%A8%8B

八、服務(wù)器部署

進(jìn)程管理pm2

cluster模式(多實(shí)例多進(jìn)程模式)啟動(dòng)服務(wù)--watch參數(shù)悲靴,意味著當(dāng)你的express應(yīng)用代碼發(fā)生變化時(shí)臭胜,pm2會(huì)幫你重啟服務(wù)。

pm2 start server.js -i 4 --watch

或者pm2 -i 4 start npm -- run start --watch(同npm run start)

查詢所有服務(wù) pm2 list


附:pm2的cluster模式官方介紹

PM2 - Cluster Modepm2.keymetrics.io/docs/usage/cluster-mode/


nginx反向代理

修改nginx.config文件癞尚,增加對(duì)應(yīng)虛擬主機(jī)反向代理到node對(duì)應(yīng)的服務(wù)端口

? ? server {

? ? ? ? listen? ? ? 80;

? ? ? ? server_name? csyry.com;

? ? ? ? location / {

? ? ? ? ? ? proxy_pass? http://127.0.0.1:8080;

? ? ? ? ? ? index? index.html index.htm;

? ? ? ? }

? ? }

重啟nginx服務(wù)器: sudo nginx -s reload

附:nginx中文配置文檔

Nginx中文文檔www.nginx.cn/doc/

修改DNS

正式環(huán)境通過(guò)域名服務(wù)商修改映射解析耸三,本機(jī)測(cè)試修改/etc/hosts文件

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市浇揩,隨后出現(xiàn)的幾起案子仪壮,更是在濱河造成了極大的恐慌,老刑警劉巖胳徽,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件积锅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡养盗,警方通過(guò)查閱死者的電腦和手機(jī)缚陷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)往核,“玉大人箫爷,你說(shuō)我怎么就攤上這事。” “怎么了蝶缀?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵丹喻,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我翁都,道長(zhǎng)碍论,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任柄慰,我火速辦了婚禮鳍悠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘坐搔。我一直安慰自己藏研,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布概行。 她就那樣靜靜地躺著蠢挡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪凳忙。 梳的紋絲不亂的頭發(fā)上业踏,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音涧卵,去河邊找鬼勤家。 笑死,一個(gè)胖子當(dāng)著我的面吹牛柳恐,可吹牛的內(nèi)容都是我干的伐脖。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼乐设,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼讼庇!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起伤提,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤巫俺,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后肿男,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體介汹,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年舶沛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嘹承。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出棚贾,到底是詐尸還是另有隱情捶牢,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布烁挟,位于F島的核電站愚隧,受9級(jí)特大地震影響个少,放射性物質(zhì)發(fā)生泄漏蒙揣。R本人自食惡果不足惜靶溜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望懒震。 院中可真熱鬧罩息,春花似錦、人聲如沸个扰。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)递宅。三九已至娘香,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間恐锣,已是汗流浹背茅主。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工舞痰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留土榴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓响牛,卻偏偏與公主長(zhǎng)得像玷禽,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子呀打,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容