最近在開發分頁式網頁時,初次把SPA專案部署到GitHub上使用。但是發現當使用者在分頁reload時,會產生404的問題。
以下就來介紹為什麼會有這種狀況發生。

404的狀況

例如使用者的情境為:

  1. 進入test.io/
  2. 打開test.io/form
  3. 重新整理test.io/form
  4. response 404 http status

你可能會覺得很疑惑,其他網站就可以為什麼這個就不行?
原因其實也很容易理解,GitHub pages只支援靜態網頁的,網址對應到的也是真實路徑。
以上面的例子來說,造訪test.io/就相當於是造訪 / 的路徑,也就是對應 /index.html。
當你造訪test.io/form時,對GitHib pages而言,/form/index.html 是不存在的,當然會拋出404 status。

SPA的路徑

所以這跟SPA有什麼關係?請注意,SPA顧名思義就是單頁式網頁,就表示他是“不換頁”的網頁,只是看起來像換頁而已。
也就是瀏覽分頁都是在同一個頁面上,所以只要重新整理,又會回到原本的分頁了。
我們通常會搭配,例如: router-dom之類的套件來達到分頁效果。
但是SPA重新整理就會遇到上面的404的狀況,那該如何在GitHub pages解決呢?

重新導向

GitHub pages有提供導向到404的custom page,所以我們可以善用這點來做一些變化。
如果我們在導向到自己製作的404頁面,並且導向到我要的頁面,這不就解決問題了嗎?
也就是說,我們可以把情境變成:

  1. 進入test.io/
  2. 打開test.io/form
  3. 重新整理test.io/form
  4. 404 導向到自己的404頁面
  5. 在404頁面分析網址,導向到 /form

流程清楚之後,再來就來談如何實作

404頁面建置

網路上有大大分享了如何重新導向的程式碼,懶得看一大串英文的這邊幫你翻譯一下。
請將以下程式碼放到404.html裡面(通常放在public)
當找不到頁面時,就會重新導向,並且將後面的參數變成query和hash夾在後面。
例如:

  • test.io/form會變成test.io/?p=form

更改location到test.io/?p=form的時候,就會回到首頁。

1
2
3
4
5
6
7
8
9
10
11
12
var segmentCount = 0;
var l = window.location;
l.replace(
l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') +
l.pathname.split('/').slice(0, 1 + segmentCount).join('/') + '/?p=/' +
l.pathname.slice(1).split('/').slice(segmentCount).join('/').replace(/&/g, '~and~') +
(l.search ? '&q=' + l.search.slice(1).replace(/&/g, '~and~') : '') +
l.hash
);

// Single Page Apps for GitHub Pages
// Copyright (c) 2016 Rafael Pedicini, licensed under the MIT License

補充:
segmentCount為階層,如果你寫成1的話:

  • test.io/form/123 會變成 test.io/form/?p=123

設定好之後,你重新整理頁面會發現404的頁面已經不再出現了。
再來就是設定導向到特定分頁的程式碼。

導入分頁

接下來我們要做的事情是,將後面的query分析成指定的路徑。
這邊會用到的是window.history.replaceState,使用他可以改變網頁路徑。
所以以下是分析網址的演算法,放到你的首頁即可(通常在public)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script type="text/javascript">
(function(l) {
if (l.search) {
var q = {};
l.search.slice(1).split('&').forEach(function(v) {
var a = v.split('=');
q[a[0]] = a.slice(1).join('=').replace(/~and~/g, '&');
});
if (q.p !== undefined) {
window.history.replaceState(null, null,
l.pathname.slice(0, -1) + (q.p || '') +
(q.q ? ('?' + q.q) : '') +
l.hash
);
}
}
}(window.location))
</script>

// Single Page Apps for GitHub Pages
// Copyright (c) 2016 Rafael Pedicini, licensed under the MIT License

再來你重新整理頁面的時候,就會發現的網址列有閃一下,那就是重新導入的動作,表示已經成功了。

前端的寫法

這邊以React為例,用react-router-dom的正常寫法即可。

1
2
3
4
5
6
7
...
<Switch>
<Route exact path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/event" component={Event} />
</Switch>
...

其他做法

事實上有其他的方法,以React為例:
可以使用HashRouter,剛剛在上面有提到,如果在網址內包含query和hash是可以的,因此就是利用這個原理。

參考資料

  1. spa-github-pages