react-router browserHistory重新整理頁面404問題解決方法
使用React開發新專案時,遇見了重新整理頁面,直接訪問二級或三級路由時,訪問失敗,出現404或資源載入異常的情況,本篇針對此問題進行分析並總結解決方案。
背景
使用webpack-dev-server做本地開發伺服器時,正常情況只需要簡單使用webpack-dev-server指令啟動即可,但是當專案處於以下兩種情況時,往往需要有巢狀路由和非同步載入路由:
- 我們使用react-router這種路由庫構建單頁面應用路由;
- 使用html-webpack-plugin外掛動態將載入js的<script>標籤注入html文件;
這時,訪問localhost:9090是可以正常載入頁面和js等檔案的,但是當我們需要訪問二級甚至三級路由或者重新整理頁面時,如localhost:9090/posts/92時,可能會出現兩種情況:
- 頁面載入失敗,返回Cannot Get(404);
- 服務響應,但是沒有返回webpack處理輸出的html檔案,導致無法載入js資源,第二種情況如圖:
那麼我們怎麼處理才能正常訪問,各頁面路由呢?博主追蹤溯源,查詢文件配置後解決了問題,本篇就是對整個解決問題過程的總結。
分析問題
發現問題後,我們就要開始分析,解決問題了,我們判斷這個問題一般是兩方面原因造成:
- react-router路前端由配置;
- webpack-dev-server服務配置;
react-router
因為前端路由更容易確定問題,更方便分析,而且對於react-router更熟悉,所以首先去查詢react-router路由庫相關配置資訊,發現文件中提到了使用browserHistory時,會建立真實的URL,處理初始/請求沒有問題,但是對於跳轉路由後,重新整理頁面或者直接訪問該URL時,會發現無法正確相應,更多資訊檢視參考文件,文件中也提供了幾種伺服器配置解決方式:
Node
const express = require('express') const path = require('path') const port = process.env.PORT || 8080 const app = express() // 通常用於載入靜態資源 app.use(express.static(__dirname + '/public')) // 在你應用 JavaScript 檔案中包含了一個 script 標籤 // 的 index.html 中處理任何一個 route app.get('*', function (request, response){ response.sendFile(path.resolve(__dirname,'public', 'index.html')) }) app.listen(port) console.log("server started on port " + port)
在使用Node作為服務時,需要使用萬用字元*監聽所有請求,返回目標html文件(引用js資源的html)。
Nginx
如果使用的是nginx伺服器,則只需要使用try_files 指令:
server { ... location / { try_files $uri /index.html } }
Apache
如果使用Apache伺服器,則需要在專案根目錄建立.htaccess檔案,檔案包含如下內容:
RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.html [L]
以下都是針對伺服器的配置,可惜的是我們目前還沒引入相關伺服器,只是使用了webpack-dev-server的內建服務,但是我們已經找到問題所在了,就是路由請求無法匹配返回html文件,所以接下來就該去webpack-dev-server文件中查詢解決方式了。
webpack-dev-server
在這裡不得不吐槽一下webpack-dev-server官方文件,博主反覆看了幾遍,才看清楚了問題所在,這裡也分兩種情況:
- 沒有修改output.publicPath,即webpack配置檔案中沒有宣告值,屬於預設情況;
- 設定了output.publicPath為自定義值;
預設情況
預設情況下,沒有修改output.publicPath值,只需要設定webpack-dev-server的historyApiFallback配置:
devServer: { historyApiFallback: true }
If you are using the HTML5 history API you probably need to serve your index.html in place of 404 responses, which can be done by setting historyApiFallback: true
如果你的應用使用HTML5 history API,你可能需要使用index.html響應404或者問題請求,只需要設定g historyApiFallback: true即可
自定義值
However, if you have modified output.publicPath in your Webpack configuration, you need to specify the URL to redirect to. This is done using the historyApiFallback.index option
如果你在webpack配置檔案中修改了 output.publicPath 值,那麼你就需要宣告請求重定向,配置historyApiFallback.index 值。
// output.publicPath: '/assets/' historyApiFallback: { index: '/assets/' }
Proxy
發現使用以上方式,並不能完全解決我的問題,總會有路由請求響應異常,於是博主繼續查詢更好的解決方案:
The proxy can be optionally bypassed based on the return from a function. The function can inspect the HTTP request, response, and any given proxy options. It must return either false or a URL path that will be served instead of continuing to proxy the request.
代理提供通過函式返回值響應請求方式,針對不同請求進行不同處理,函式引數接收HTTP請求和響應體,以及代理配置物件,這個函式必須返回false或URL路徑,以表明如何繼續處理請求,返回URL時,源請求將被代理到該URL路徑請求。
proxy: { '/': { target: 'https://api.example.com', secure: false, bypass: function(req, res, proxyOptions) { if (req.headers.accept.indexOf('html') !== -1) { console.log('Skipping proxy for browser request.'); return '/index.html'; } } } }
如上配置,可以監聽https://api.example.com域下的/開頭的請求(等效於所有請求),然後判斷請求頭中accept欄位是否包含html,若包含,則代理請求至/index.html,隨後將返回index.html文件至瀏覽器。
解決問題
綜合以上方案,因為在webpack配置中修改了output.publicPath為/assets/,所以博主採用webpack-dev-server Proxy代理方式解決了問題:
const PUBLICPATH = '/assets/' ... proxy: { '/': { bypass: function (req, res, proxyOptions) { console.log('Skipping proxy for browser request.') return `${PUBLICPATH}/index.html` } } }
監聽所有前端路由,然後直接返回${PUBLICPATH}/index.html,PUBLICPATH就是設定的output.publicPath值。
另外,博主總是習慣性的宣告,雖然不設定該屬性也能滿足預期訪問效果:
historyApiFallback: true