摘要:現(xiàn)在我們需要在服務(wù)端和瀏覽器之間開(kāi)啟一個(gè)中間層用于服務(wù)端渲染。問(wèn)題設(shè)置的配置文件這樣我們的層才能獲取到的,并在客戶端將登陸時(shí)將保存下來(lái),同時(shí)返回給客戶端。這樣用戶在刷新頁(yè)面時(shí),會(huì)通過(guò)并帶上請(qǐng)求服務(wù)器獲取數(shù)據(jù)。
vue 博客優(yōu)化,服務(wù)端渲染(SSR)指南 對(duì)已有的單頁(yè)應(yīng)用進(jìn)行改造,優(yōu)化,使之成為一個(gè)具有良好seo的應(yīng)用
github地址
WdBly博客地址
git clone https://github.com/WdBly/my-blog-WdBly.git服務(wù)端配置:
cd server && composer install 配置.env文件 包含了SESSION_DOMAIN APP_KEY DATABASE的相關(guān)信息 配置目錄權(quán)限 配置nginx 參考下方配置客戶端配置
cd web && npm install npm run build-client && npm run build-server啟動(dòng)客戶端
node ssr后臺(tái)啟動(dòng) 需要pm2
npm install pm2 -g pm2 start ssr優(yōu)化進(jìn)度
[x] webpack開(kāi)啟vendor分包,壓縮代碼,異步路由組件
[x] nginx gzip
[x] 服務(wù)端渲染
[x] cookie轉(zhuǎn)發(fā)
[x] 圖片cdn
[x] 部分https
Vue 項(xiàng)目改造 - 服務(wù)端渲染幾個(gè)問(wèn)題:使用服務(wù)端渲染解決了什么問(wèn)題?,技術(shù)上如何實(shí)現(xiàn)? 經(jīng)過(guò)服務(wù)端渲染改造的項(xiàng)目和改造前的單頁(yè)的區(qū)別?
場(chǎng)景:已有基于vue-cli的單頁(yè)博客項(xiàng)目,前端使用 vue+vue-router+vuex+axios+elementui+webpack,后臺(tái)使用laravel + mysql
,服務(wù)器阿里云 Ubuntu 16.04,web服務(wù)器nginx。
面臨的問(wèn)題。1:?jiǎn)雾?yè)應(yīng)用首屏加載過(guò)慢;2:無(wú)法被搜索引擎抓取;3:首屏白屏?xí)r間過(guò)長(zhǎng)(重要);
解決一:首屏加載過(guò)慢。
經(jīng)過(guò)分析,頁(yè)面首屏慢主要是首次需要加載的js文件過(guò)大。
1:對(duì)webpack打包過(guò)程進(jìn)行優(yōu)化,采用多入口將項(xiàng)目的vender依賴分割,對(duì)不需要變動(dòng)的文件做緩存,同時(shí)對(duì)路由做異步加載。
//多個(gè)入口 entry: { "app": path.join(projectRoot, "entry-client.js"), "vendor": ["vue", "vue-router", "vuex", "element-ui"] } //對(duì)多入口的文件 多帶帶打包 并且不開(kāi)啟hash保證緩存 new webpack.optimize.CommonsChunkPlugin({ name: "vendor", filename: "client/[name].bundle.js", }) //js壓縮 縮減文件大小 new UglifyJsPlugin({ uglifyOptions: { compress: { warnings: false } }, sourceMap: true, parallel: true }) //異步的路由 使得首屏加載的代碼盡量小 const Home = ()=> import("@/components/Home.vue");
2:服務(wù)端nginx開(kāi)啟gzip壓縮
gzip on; gzip_disable "msie6"; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_buffers 16 8k; gzip_http_version 1.1; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
經(jīng)過(guò)驗(yàn)證 開(kāi)啟gzip壓縮的js文件大小大概能縮減為源文件的1/5
通過(guò)前面兩部的優(yōu)化,首屏加載快了不少,但還是有點(diǎn)慢。
解決二三:SEO和白屏的處理
開(kāi)啟服務(wù)端的渲染首先我們來(lái)捋一捋實(shí)現(xiàn)流程,傳統(tǒng)的單頁(yè)應(yīng)用的流程為前端將文件打包后生成了index.html文件和其他依賴文件,index.html文件中引入了一些js文件和css文件。如下:
nginx中配置相應(yīng)的server_name和root字段,兩個(gè)路由分別對(duì)應(yīng)前端頁(yè)面和后臺(tái)接口。
//前端頁(yè)面的路由 server { root /www/wwwroot/myblog-WdBly/web/dist server_name www.wddsss.com ... } //后臺(tái)接口路由 server { root /www/wwwroot/myblog-WdBly/server/public server_name api.wddsss.com ... }
用戶訪問(wèn)前端路由www.wddsss.com時(shí) 會(huì)返回dist目錄下的index.html文件給瀏覽器。剩下的所有工作都由瀏覽器完成。
現(xiàn)在我們需要在nginx服務(wù)端和瀏覽器之間開(kāi)啟一個(gè)node中間層用于服務(wù)端渲染。
理想狀態(tài)是 當(dāng)用戶訪問(wèn) www.wddsss.com時(shí),nginx通過(guò)proxy_pass將訪問(wèn)流量代理到node中間層監(jiān)聽(tīng)的端口,而不是直接返回一個(gè)index.html文件,nginx將后面的返回頁(yè)面的工作交給了node。nginx代理配置:
//node監(jiān)聽(tīng)了 5006端口,注意我們并不對(duì)外部暴露5006端口,也就是說(shuō)通過(guò) //www.wddsss.com:5006的訪問(wèn)是會(huì)失敗的。 upstream z.com { server 127.0.0.1:5006; } //proxy_pass 將訪問(wèn)轉(zhuǎn)移到 127.0.0.1:5006 server { server_name www.wddsss.com; access_log /var/log/nginx/blog.api.access.log; error_log /var/log/nginx/blog.api.error.log; location / { proxy_pass http://z.com; } }
現(xiàn)在的任務(wù)清晰了不少,我們需要開(kāi)啟一個(gè)node服務(wù),監(jiān)聽(tīng)一個(gè)端口,當(dāng)有用戶訪問(wèn)的時(shí)候完成一大波事情,最后需要返回一個(gè)充滿數(shù)據(jù)的html文件。
那么我們開(kāi)始實(shí)現(xiàn)這個(gè)任務(wù)吧!,首先分析,用戶訪問(wèn)www.wddsss.com/app/home這個(gè)路由時(shí),我們?cè)趎ode中監(jiān)聽(tīng)到訪問(wèn),必然需要將此路由對(duì)應(yīng)的組件,以及組件中需要的數(shù)據(jù)獲取并整合形成html文件。
vue-server-renderer 提供了一個(gè)renderToString方法,此方法接受一個(gè)Vue組件,返回一段對(duì)應(yīng)的html代碼。這不就解決了我們的問(wèn)題嘛。
重新整理思路,node在監(jiān)聽(tīng)到某個(gè)路由被訪問(wèn)時(shí),會(huì)去查找前端路由表,并找到對(duì)應(yīng)的組件。對(duì)于某些需要ssr的組件,我們手動(dòng)為其添加了一個(gè)asyncData()方法,在node加載這些組件同時(shí)會(huì)去執(zhí)行asyncData()方法,拿到組件內(nèi)的數(shù)據(jù)渲染到組件中。最后將這個(gè)組件傳入renderToString方法。這樣一個(gè)簡(jiǎn)陋無(wú)比的ssr就做好了!部分代碼如下(刪減版,完整的請(qǐng)前往github查看)
const { createRenderer } = require("vue-server-renderer") const createApp = require("./dist/bundle.server.js")["default"] const renderer = createRenderer({ template: require("fs").readFileSync("ssr/view/index.template.html", "utf-8") }) const data = { script: ` `, state: `` } express.get("*", (req, res) => { const context = {url: req.url}; createApp(context).then(app => { var state = JSON.stringify(context.state); data.state = `` renderer.renderToString(app, data, (err, html) => { res.end(html) }) }) })
1:我們可以看到createRenderer方法可以接受一個(gè)html模板文件,因?yàn)閞enderToString方法最終生成的html片段時(shí)不帶head頭等內(nèi)容的,需要我們自定義一個(gè)模板,將renderToString方法生成的html插入到次模板html文件即可。
2:createApp變量來(lái)自一個(gè)bundle.server.js,這個(gè)js文件是通過(guò)特定的配置對(duì)項(xiàng)目打包后生成的入口文件,它接受一個(gè)context用于根據(jù)路由尋找對(duì)應(yīng)組件并將最終的state添加至context,后面會(huì)說(shuō)到。
3:createApp本身是一個(gè)異步過(guò)程,因?yàn)樵谶@個(gè)函數(shù)中可能會(huì)存在數(shù)據(jù)的獲取,當(dāng)數(shù)據(jù)獲取且組件加載完畢后,執(zhí)行then中的renderToString,renderToString的第二個(gè)data參數(shù)即是傳入模板html文件的參數(shù)。
webpack.server.js部分配置
target: "node", entry: [path.join(projectRoot, "entry-server.js")], output: { libraryTarget: "commonjs2", path: path.join(projectRoot, "dist"), filename: "bundle.server.js", chunkFilename: "[name].bundle.js", publicPath: "/" }
這個(gè)比較簡(jiǎn)陋的ssr存在很多問(wèn)題,主要是客戶端拿到的只是一個(gè)多帶帶的html頁(yè)面其中,我們綁定的事件,統(tǒng)統(tǒng)是不生效的。
正確的流程是當(dāng)客戶端拿到首屏渲染好的頁(yè)面時(shí),會(huì)在瀏覽器后臺(tái)執(zhí)行一次重繪,生成一系列的虛擬Node,并且和從服務(wù)端獲取的真實(shí)dom節(jié)點(diǎn)進(jìn)行比對(duì),若是不匹配,會(huì)執(zhí)行重繪(使用瀏覽器端生成的頁(yè)面),而瀏覽器在后臺(tái)生成虛擬Node依賴于頁(yè)面中的數(shù)據(jù),而我們又不可能在瀏覽器再次發(fā)送ajax請(qǐng)求來(lái)獲取頁(yè)面數(shù)據(jù)(浪費(fèi)),所有在上方代碼中我們可以看到window.__INITIAL_STATE__這一句,當(dāng)服務(wù)端獲取到組件的數(shù)據(jù)時(shí),會(huì)將state放在script標(biāo)簽的一個(gè)變量中,在客戶端執(zhí)行重繪時(shí)采用的即是這里的數(shù)據(jù)。
具體過(guò)程參考下圖:
在整個(gè)過(guò)程中產(chǎn)生的一些問(wèn)題總結(jié)
element-ui樣式丟失問(wèn)題。在 app.js 中我們引入了 "element-ui/lib/theme-chalk/index.css"的css文件,我們必須要清楚 app.js本身會(huì)在服務(wù)端執(zhí)行,所以我們必須在webpack.server.js中配置處理css文件的loader
{ test:/.css$/, use:["vue-style-loader", "css-loader"], }區(qū)分是當(dāng)前執(zhí)行環(huán)境時(shí)node還是瀏覽器
因?yàn)槲覀兊捻?xiàng)目是要在服務(wù)端執(zhí)行,同時(shí)也會(huì)在客戶端執(zhí)行,到時(shí)服務(wù)端不支持某些客戶端對(duì)象 如window對(duì)象,所以在我們的代碼中如果有使用到window,document等瀏覽器API的地方需要對(duì)當(dāng)前的執(zhí)行環(huán)境進(jìn)行判斷
我們使用的方式是webpack的插件
new webpack.DefinePlugin({ "process.env.VUE_ENV": ""client"", "process.env.NODE_ENV": ""production"", }), new webpack.DefinePlugin({ "process.env.VUE_ENV": ""server"", "process.env.NODE_ENV": ""production"", }),某些頁(yè)面與用戶是否登陸相關(guān)
對(duì)于某些頁(yè)面,我們可能會(huì)在頁(yè)面中顯示當(dāng)前登陸用戶的信息,并將這個(gè)信息存入了localStorage中,但結(jié)合上個(gè)問(wèn)題我們可以看出來(lái),在服務(wù)端渲染中我們并不能獲取到這個(gè)localStorage這個(gè)對(duì)象。那么最終渲染出的頁(yè)面在和瀏覽器重繪的頁(yè)面進(jìn)行對(duì)比時(shí)必然會(huì)出現(xiàn)不匹配的錯(cuò)誤。
處理方法,在node層為登陸用戶設(shè)置cookie,當(dāng)用戶請(qǐng)求時(shí),若是判斷出當(dāng)前的執(zhí)行環(huán)境為node,則從cookie中讀取信息載入頁(yè)面,否則從localStorage讀取數(shù)據(jù)。
除了上述情況可能導(dǎo)致兩個(gè)渲染不匹配,還有從服務(wù)端返給客戶端的__INITIAL_STATE__不存在或者_(dá)_INITIAL_STATE__的內(nèi)容有誤時(shí),都會(huì)導(dǎo)致客戶端獲取不到初始__INITIAL_STATE__而發(fā)生不匹配的錯(cuò)誤(這里__INITIAL_STATE__不存在的情況有很多種),
1:若是直接將 做為參數(shù)傳入index.template.html,那么需要使用{{{ }}}的語(yǔ)法解析。
2:在index.template.html 引入state的標(biāo)簽需要在引入build.client.js的標(biāo)簽之前引入,因?yàn)閎uild.client.js需要依賴初始state。
3:若是state中存在標(biāo)簽(比如mackdown語(yǔ)法生成的dom結(jié)構(gòu))需要使用不轉(zhuǎn)義插值{{{}}}。
4: 若是state中有某些特殊字符 :: 回車(chē)等特殊字符,需要使用{{}}進(jìn)行轉(zhuǎn)移,否則在渲染頁(yè)面時(shí)這部分state會(huì)直接跑到頁(yè)面上去。
laravel設(shè)置的cookie配置 .env 文件 SESSION_DOMAIN=.wddsss.com
這樣我們的node層(www.wddsss.com)才能獲取到laravel的cookie,并在客戶端將登陸時(shí)將cookie保存下來(lái),同時(shí)返回給客戶端。這樣用戶在刷新頁(yè)面時(shí),node會(huì)通過(guò)axios并帶上cookie請(qǐng)求nginx服務(wù)器獲取數(shù)據(jù)。
我們的首頁(yè)圖片加載可以明細(xì)看到很慢(畢竟1M的小服務(wù)器),開(kāi)啟七牛cdn,具體流程就不說(shuō)了,使用的是laravel itbdw/laravel-storage-qiniu包。
開(kāi)啟https到這個(gè)時(shí)候我們發(fā)現(xiàn)網(wǎng)站被標(biāo)記為不安全了,https走一波,推薦一個(gè)免費(fèi)證書(shū)申請(qǐng)機(jī)構(gòu)。
certbot證書(shū)申請(qǐng)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/99150.html
摘要:現(xiàn)在我們需要在服務(wù)端和瀏覽器之間開(kāi)啟一個(gè)中間層用于服務(wù)端渲染。問(wèn)題設(shè)置的配置文件這樣我們的層才能獲取到的,并在客戶端將登陸時(shí)將保存下來(lái),同時(shí)返回給客戶端。這樣用戶在刷新頁(yè)面時(shí),會(huì)通過(guò)并帶上請(qǐng)求服務(wù)器獲取數(shù)據(jù)。 vue 博客優(yōu)化,服務(wù)端渲染(SSR)指南 對(duì)已有的單頁(yè)應(yīng)用進(jìn)行改造,優(yōu)化,使之成為一個(gè)具有良好seo的應(yīng)用 github地址WdBly博客地址 安裝 git clone htt...
摘要:后端主要使用的框架,數(shù)據(jù)庫(kù)采用。后臺(tái)管理登錄采用與后端進(jìn)行登陸狀態(tài)的確認(rèn)。本文首發(fā)于小站,這是一個(gè)積累和分享知識(shí)的個(gè)人博客 這篇文章擱置了很長(zhǎng)時(shí)間,最終決定還是把它寫(xiě)出來(lái),給剛開(kāi)始學(xué)習(xí)vue并且想用vue寫(xiě)個(gè)人博客的同學(xué)一個(gè)參考。因?yàn)楫?dāng)初我也是參考了其他人分享的知識(shí),從一個(gè)vue小白變成了一個(gè)入門(mén)級(jí)選手,并最終完成了這個(gè)個(gè)人博客的搭建工作,代碼已托管在Github-justJokee。...
摘要:對(duì)于客戶端應(yīng)用來(lái)說(shuō),服務(wù)端渲染是一個(gè)熱門(mén)話題。在服務(wù)器預(yù)渲染初始應(yīng)用狀態(tài)。重構(gòu)這段腳本,使其可以在服務(wù)端運(yùn)行。如果這些原因和你的情況吻合,那么使用進(jìn)行服務(wù)端渲染將會(huì)是個(gè)不錯(cuò)方案。我已經(jīng)發(fā)布兩個(gè)庫(kù)來(lái)支持的服務(wù)端渲染和專為應(yīng)用打造的。 showImg(https://segmentfault.com/img/remote/1460000014155032);對(duì)于客戶端應(yīng)用來(lái)說(shuō),服務(wù)端渲染是...
摘要:斯坦福宣布使用作為計(jì)算機(jī)課程的首選語(yǔ)言近日,某位有年教學(xué)經(jīng)驗(yàn)的斯坦福教授決定放棄,而使用作為計(jì)算機(jī)入門(mén)課程的教學(xué)語(yǔ)言。斯坦福官方站點(diǎn)將它們新的課程描述為是最流行的構(gòu)建交互式的開(kāi)發(fā)語(yǔ)言,本課程會(huì)用講解中的實(shí)例。 前端每周清單第 11 期:Angular 4.1支持TypeScript 2.3,Vue 2.3優(yōu)化服務(wù)端渲染,優(yōu)秀React界面框架合集 為InfoQ中文站特供稿件,首發(fā)地址為...
閱讀 2236·2021-11-24 11:15
閱讀 3080·2021-11-24 10:46
閱讀 1378·2021-11-24 09:39
閱讀 3924·2021-08-18 10:21
閱讀 1478·2019-08-30 15:53
閱讀 1395·2019-08-30 11:19
閱讀 3320·2019-08-29 18:42
閱讀 2321·2019-08-29 16:58