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