關於Node服務端渲染
什麼是服務端渲染
服務端渲染也稱作 SSR(Server Side Render) 。不同於客戶端渲染,服務端渲染會在後端把頁面 DOM 的結構樹轉成 String 吐出來,然後到前端(如瀏覽器)解析渲染。
優勢
SEO
現在單頁面應用由於體驗好,廣泛流行。但單頁應用的做法往往是後端只吐出一個頁面的框架,裡面沒有具體內容,然後前端通過 Ajax 動態拉取內容。這就導致爬蟲去訪問你的站點時,服務端返回給爬蟲的只有一個架子,爬蟲無法抓取頁面關鍵詞之類等信息。
首屏直出
意思很好理解,就是在用戶首次訪問的時候不用再看到菊花在那裡轉呀轉 (Loading...) ,首屏就可以看到頁面所有內容。另外可以在服務端通過 HTTP 介面合併請求等方式,讓頁面打開的首屏時間縮短。
Node 服務端渲染有什麼特別?
同構(isomorphic)!我想這個應該是用 Node 做服務端渲染最大的優勢。那麼什麼是同構呢?
其實同構大多是由 isomorphic 單詞翻譯來的,這個單詞含義比較難理解,現在也有很多叫做 universal app。意思差不多,就是說能夠實現一套代碼在服務端跟客戶端同時運行。
假如我們客戶端的頁面是 React 寫的,那麼這套代碼也能在服務端運行,並進行渲染,這就是同構的概念。同一份代碼,運行構建於兩端。因為都是 javascript 語法,所以用 Node 做伺服器渲染在這方面有天生的優勢。
用 Node 做同構有什麼難點?
運行環境支持
現在的前端開發,大多數用的是 ES6/7 的語法,然後用 Babel 編譯成 ES5/3 後讓瀏覽器運行。 Node 對 ES6/7 的支持並不是十分友好,就算是最新版本的 node,也不還是不支持 ES Module (就是我們常看到的 import 語法引入模塊)。所以要達到同一份代碼兩端運行的目的,就必須磨平運行環境的差異。
那麼該如何做呢?答案就是 babel-register 。
babel-register 模塊會改寫 require 命令,為它加上一個鉤子。此後,每當使用 require 載入 .js 、 .jsx 、 .es 和 .es6 後綴名的文件,就會先用 Babel 進行轉碼。當然,這就要求你必須在你服務端入口文件的頂部率先載入這個模塊。
資源載入
如果說磨平環境差異還不算困難,那讓 Node 支持多種資源類型載入估計是要讓你頭皮發麻了。
比如說我們現在用 react 開發 app ,app 中必然涉及到 css/scss 、 png/jpg 、 font 等文件的載入吧?我們一般是通過webpack的loader來處理的,那這段代碼運行在服務端會怎麼樣?必然是血崩。。。
node `require` 默認就只支持載入 .json .js 等幾種文件,所以如何保證客戶端渲染出來的代碼跟服務端渲染出來的代碼一致呢(在 react 應用中,react 會檢查客戶端渲染出來的結構是否跟客戶端渲染出來的一致,如果不一致的話,會在客戶端重渲染)?這裡提供兩套思路:
1. 客戶端跟服務端用同一套 webpack 打包後的資源。webpack-isomorphic-tools 可以很好的解決這個問題,或者最新的 webpack 版本 target: node 也能實現。
2. png/jpg/font 等文件直接忽略(在 babel-register 里可以設置),scss/css的話,用 css in js 的方式寫。
總結
Node服務端渲染好處多多,但除了上述技術性的問題需要解決外,仍然有些線上問題需要注意。
首當其衝的就是伺服器 cpu 過高問題,因為現在頁面結構是在服務端以 `renderToString` 的方式輸出,所以頁面請求路由會涉及到大量的計算。這就會導致如果頁面並發高一點的話,會出現 cpu 過高的問題。
另外在服務端可沒有什麼 window 、 document 對象,這些東西也需要去 hook 掉;在 React 應用中,componentDidMount 等生命周期函數也不會在服務端觸發;定時器記得及時釋放,否則可能會導致內存泄露的風險!
如果你確定要用 node 做服務端渲染的話,建議你應該用一些開源成熟的框架。比如在 react 體系下比較有代表性的 [next.js](zeit/next.js), vue 體系下的 [Nuxt.js](Universal Vue.js Applications)。
推薦閱讀: