我之前在上線自己的部落格遇到過下面這些問題
- 為啥我的部落格在開發階段都沒問題,部署到伺服器之後訪問不了除了
/
的頁面 - 路由用hash模式就沒問題,改成history就會有問題
- 公眾號:前端南玖
- 不定時有送書活動,記得關注~
- 每日推送前端技術文章~
什麼是路由
在Web開發過程中,經常會遇到『路由』的概念。那麼,到底什麼是路由?簡單來說,路由就是URL到函式的對映。
並且路由這個概念最早是出現在後端的,因為早期的網頁都是服務端渲染的,比如:JSP,PHP,ASP等語言,都是直接返回渲染好的html給客戶端顯示。
後端路由
在web開發早期的年代裡,前端的功能遠不如現在這麼強大,一直是後端路由佔據主導地位。無論是jsp,還是php、asp,使用者能通過URL訪問到的頁面,大多是通過後端路由匹配之後再返回給瀏覽器的。瀏覽器在位址列中切換不同的URL時,每次都向後臺伺服器發出請求,伺服器響應請求,在後臺拼接html檔案返回給前端,並且每次切換頁面時,瀏覽器都會重新整理頁面。
在後端,路由對映表中就是不同的URL地址與不同的html + css + 後端資料 之間的對映
下面這張圖或許能夠讓你更加清楚的瞭解後端路由:
前端路由
前端路由是後來發展到SPA(單頁應用)時才出現的概念。
前端路由主要是有兩種模式:
- hash: 帶有hash的前端路由,優點是相容性高。缺點是URL帶有
#
號不好看 - **history: **不帶hash的前端路由,優點是URL不帶
#
號,好看。缺點是既需要瀏覽器支援也需要後端伺服器支援
前端路由應用最廣泛的例子就是當今的SPA的web專案。不管是Vue、React還是Angular的頁面工程,都離不開相應配套的router工具。
整個頁面就只有一整套的HTMLCSS+JS,這一套HTML+CSS+JS中包含了許多個網頁,當我們請求不同的URL時,客戶端會從這一整套HTML+CSS+JS中找到對應的HTML+CSS+JS,將它們解析執行,渲染在頁面上。
前端路由帶來的最明顯的好處就是,位址列URL的跳轉不會白屏了——這也得益於客戶端渲染帶來的好處。
我們同樣來看一張圖:
Hash模式
早起的前端路由實現就是基於
location.hash
來實現的,location.hash
就是路由#
後面的內容,其原理就是通過hashchange
監聽#
後面的內容的變化來進行頁面更新。hash
模式是利用瀏覽器不會對#
後面的路徑對服務端發起請求。
使用hash模式有兩個特點:
- 改變hash值,瀏覽器不會重新載入頁面
- 當重新整理頁面時,hash不會傳給伺服器
也就是說http://localhost/#a
與http://localhost/
這兩個路由其實都是去請求http://localhost
這個頁面的內容,至於為什麼它們可以渲染出不同的頁面,這個是前端自己來判斷的,不需要後端配置。
優點:
- 可以相容低版本瀏覽器,支援IE8
- 只有
#
之前的內容才會作為URL傳送給伺服器,就算服務端沒有對路由進行全覆蓋也不會返回404 hash
改變都會在瀏覽器訪問的歷史記錄中增加一個記錄,所以可以通過瀏覽器進行前進後退,如果想在hash模式下不儲存記錄,可以使用location.replace
History模式
history
是基於HTML5新增的pushState
和replaceState
兩個API以及瀏覽器的popState
事件監聽歷史棧的改變,只要歷史棧有資訊發生改變,就會觸發該事件。這種模式同樣也是不會向後端發起請求的。
優點:
- 該模式的路由不帶
#
,看起來更美觀 pushState
設定的URL
可以是任意的與當前URL同源的URL,而hash
只能改變#後面的內容
缺點:
- IE9及其以下版本瀏覽器不支援,IE10開始支援
vue-router會檢測瀏覽器版本,當無法啟用history
模式時會自動降級為hash
模式
文章開頭說的問題是什麼原因導致的?
為啥我的部落格在開發階段都沒問題,部署到伺服器之後訪問不了除了/
的頁面??
那是因為vue-cli
在開發模式下幫你啟動的那個express
開發伺服器幫你做了這方面的配置。理論上我們在開發模式下本來也是需要配置服務端的,只不過vue-cli
都幫你配置好了,所以你就不用手動配置了。
那麼該如何配置呢?
其實在生產模式下配置也很簡單,參考vue-router給出的配置例子。一個原則就是,在所有後端路由規則的最後,配置一個規則,如果前面其他路由規則都不匹配的情況下,就執行這個規則——把構建好的那個index.html
返回給前端。這樣就解決了後端路由丟擲的404的問題了,因為只要你輸入了http://localhost/user/1
這地址,那麼由於後端其他路由都不匹配,那麼就會返回給瀏覽器index.html
。
瀏覽器拿到這個html之後,router庫就開始工作,開始獲取位址列的URL資訊,然後再告訴前端庫(比如Vue)渲染對應的頁面。到這一步就跟hash模式是類似的了。
當然,由於後端無法丟擲404的頁面錯誤,404的URL規則自然是交給前端路由來決定了。你可以自己在前端路由裡決定什麼URL都不匹配的404頁面應該顯示什麼。
頁面渲染流程
- 瀏覽器通過DNS伺服器得到域名的IP地址,向這個IP地址請求得到HTML文字
- 瀏覽器渲染程式解析HTML文字,構建DOM樹
- 解析HTML的同時,如果遇到內聯樣式或者樣式檔案,則下載並構建樣式規則,如果遇到JavaScript指令碼,則會下載執行指令碼
- DOM樹和CSSOM構建完成之後,渲染程式將兩者合併成渲染樹(render tree)
- 渲染程式開始對渲染樹進行佈局,生成佈局樹(layout tree)
- 渲染樹對佈局樹進行繪製,生成繪製記錄
服務端渲染(SSR)
顧名思義,服務端渲染就是在瀏覽器請求頁面URL時,服務端直接將我們需要的HTML文字組裝好,並返回給瀏覽器,這個HTML文字被瀏覽器解析之後,不需要經過JavaScript指令碼的執行,可直接構建出完整的DOM樹並展示頁面中。這個服務端組裝的過程,叫做服務端渲染。
客戶端渲染(CSR)
頁面的渲染其實就是瀏覽器將HTML文字轉化為頁面幀的過程。而如今我們大部分WEB應用都是使用 JavaScript 框架(Vue、React、Angular)進行頁面渲染的,也就是說,在執行 JavaScript 指令碼的時候,HTML頁面已經開始解析並且構建DOM樹了,JavaScript 指令碼只是動態的改變 DOM 樹的結構,使得頁面成為希望成為的樣子,這種渲染方式叫動態渲染
,也可以叫客戶端渲染(client side rende)
。
前端渲染把渲染的任務交給了瀏覽器,通過客戶端的算力來解決頁面的構建,這個很大程度上緩解了服務端的壓力。而且配合前端路由,無縫的頁面切換體驗自然是對使用者友好的。不過帶來的壞處就是對SEO不友好。
需要明確的是,只要在瀏覽器位址列輸入URL再回車,是一定會去後端伺服器請求一次的。而如果是在頁面裡通過點選按鈕等操作,利用router庫的api來進行的URL更新是不會去後端伺服器請求的。
前端路由與服務端渲染
雖然前端渲染有諸多好處,不過SEO的問題,還是比較突出的。所以react、vue等框架在後來也在服務端渲染上做著自己的努力。基於前端庫的服務端渲染跟以前基於後端語言的服務端渲染又有所不同。前端框架的服務端渲染大多依然採用的是前端路由,並且由於引入了狀態統一、vnode等等概念,它們的服務端渲染對伺服器的效能要求比php等語言基於的字串填充的模板引擎渲染對於伺服器的效能要求高得多。所以在這方面不僅是框架本身在不斷改進演算法、優化,服務端的效能也必須要有所提升。
當然在二者之間,也出現了預渲染的概念。也即先在服務端構建出一部分靜態的html檔案,用於直出瀏覽器。然後剩下的頁面再通過常用的前端渲染來實現。通常我們可以把首頁採用預渲染的方式。這個的好處是明顯的,兼顧了SEO和伺服器的效能要求。不過它無法做到全站SEO,生產構建階段耗時也會有所提高,這也是遺憾所在。
關於預渲染,可以考慮使用prerender-spa-plugin這個webapck的外掛,它的3.x版本開始使用puppeteer來構建html檔案了。