使用React開發新項目時,碰見了刷新頁面,直接訪問二級或三級路由時,訪問失敗,出現404或資源加載異常的狀況,本篇針對此問題進行分析並總結解決方案。html
背景前端
使用webpack-dev-server作本地開發服務器時,正常狀況只須要簡單使用webpack-dev-server指令啓動便可,可是當項目處於如下兩種狀況時,每每須要有嵌套路由和異步加載路由:react
這時,訪問localhost:9090是能夠正常加載頁面和js等文件的,可是當咱們須要訪問二級甚至三級路由或者刷新頁面時,如localhost:9090/posts/92時,可能會出現兩種狀況:webpack
那麼咱們怎麼處理才能正常訪問,各頁面路由呢?博主追蹤溯源,查找文檔配置後解決了問題,本篇就是對整個解決問題過程的總結。nginx
分析問題git
發現問題後,咱們就要開始分析,解決問題了,咱們判斷這個問題通常是兩方面緣由形成:github
react-routerweb
由於前端路由更容易肯定問題,更方便分析,並且對於react-router更熟悉,因此首先去查詢react-router路由庫相關配置信息,發現文檔中提到了使用browserHistory時,會建立真實的URL,處理初始/請求沒有問題,可是對於跳轉路由後,刷新頁面或者直接訪問該URL時,會發現沒法正確相應,更多信息查看參考文檔,文檔中也提供了幾種服務器配置解決方式:express
Nodeapi
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-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