Angular アプリを GitHub Pages に公開する際、ルーティングによる 404 を回避する、具体的な実装方法
以前書いた以下の記事の詳説。
目次
- 事象のおさらい
- アプリのビルド時は Base Href を変更する
404.html
からリダイレクトする- AppComponent でリダイレクト処理を行う
- そもそも HashLocationStrategy を使う?
事象のおさらい
Angular アプリを GitHub Pages で公開すると、そのままではトップページの URL しか有効にならない。
例えば拙作の Angular Utilities でいうと、トップページの URL は https://neos21.github.io/angular-utilities/
だが、ある画面に遷移すると、https://neos21.github.io/angular-utilities/text-converter/case-converter
という URL がブラウザのアドレスバーに表示される。
そこでこの URL をコピーして、別のタブにでも貼り付けて遷移しようとしてみると、/text-converter/case-converter/
やその配下に index.html
というファイルはないので、404 エラーになってしまう。GitHub Pages は Angular のルーティング URL を考慮してはくれないのだ。
そこでコレを自前でなんとかしよう、というのが今回の記事。
アプリのビルド時は Base Href を変更する
GitHub Pages にデプロイする Angular アプリは、--base-href
オプションをデフォルトの /
ではなく ./
という相対パスに変更してやらないと、上手くルーティングが機能しない。よって、ビルド時のコマンドは以下のようになるだろう。
$ ng build --base-href ./ --prod
このまま package.json
に npm-scripts として記載しておいて良いだろう。
404.html
からリダイレクトする
前回の記事でも書いたが、GitHub Pages は、404.html
というファイルがあると、それを 404 ページとして利用してくれる機能がある。コレを利用して、まずは 404 になる URL で遷移してきた時に、その URL を記録しつつ、Angular アプリのトップページに遷移させる。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script>
sessionStorage.redirect = location.href;
</script>
<meta http-equiv="refresh" content="0;URL=/angular-utilities/">
<title>Angular Utilities</title>
</head>
<body>
<a href="/angular-utilities/">Angular Utilities</a>
</body>
</html>
どうやら IE だけ、404 ページのサイズが 512 バイト以下でないと表示してくれないようなので、必要最小限の内容にしておく。ちなみに上述のコードで、インデントなど諸々込みで 343 バイト。必要に応じて改行やインデントを削り調整しよう。
このファイルでやっているのは、meta
要素によるリダイレクトと、SessionStorage に叩かれた URL を記録するという処理。このままリダイレクトすると、Angular アプリの起動時に SessionStorage から URL 情報が拾える、というワケ。
AppComponent でリダイレクト処理を行う
というワケで、リダイレクトが行われて、Angular アプリが起動する時に、ngOnInit()
にてリダイレクト処理を行ってあげる。
export class AppComponent implements OnInit {
constructor(private router: Router) { }
public ngOnInit(): void {
// SessionStorage より 404.html からの遷移元 URL 情報を取得する
const redirectUrl = sessionStorage.redirect;
// SessionStorage の情報は削除しておく
delete sessionStorage.redirect;
// 遷移元 URL が取得できていた場合 (もしトップページの URL が取得できた場合は移動する必要ないので無視)
if(redirectUrl && redirectUrl !== location.href) {
// ハッシュやパラメータ「#」「?」「;」を除去する : ココはアプリの要件に応じて、ハッシュやパラメータを別途再現するために抽出して処理分けする
const pureUrl = redirectUrl.split(/#|\?|;/)[0];
// アドレスバーの URL (履歴) を修正する
window.history.replaceState(null, null, pureUrl);
// ドメイン・アプリルート部分 ('https://neos21.github.io/angular-utilities/' の部分) を削除する → 配下のパス文字列だけが残る
const navigateUrl = pureUrl.replace(/http.*:\/\/.*\/angular-utilities/, '');
// パス文字列を渡して遷移する
this.router.navigate([navigateUrl])
.catch(() => {
// 遷移エラー時 (受け取った URL が不正だった場合など) は仕方ないのでトップページに遷移する
this.router.navigate(['']);
});
}
}
}
SessionStorage に保存させた location.href
はフルパスなので、Router#navigate()
に渡すための加工をしている。パラメータを渡したい場合は、別途上手くちぎって、Router#navigate()
の第2引数に渡してやったりする必要がある。
もしも整形した URL に移動できない場合は、Router#navigate()
が例外を吐くので、.catch()
で処理してやる。こうすれば、ルーティングモジュールで '**'
でのリダイレクトを宣言していなくても上手くいく。
コレで完了。
そもそも HashLocationStrategy を使う?
ところで、そもそもユーザに見せる URL が、実は直接遷移しているワケではなく、このような奇妙なリダイレクトをしている、というのは、気持ち悪さがある。
そこで、別の方法として、Angular デフォルトのルーティングの仕組みである PathLocationStrategy ではなく、ハッシュを利用した HashLocationStrategy を利用できる。
HashLocationStrategy は、一昔前の Ajax ページで流行った、#!
で始まる Shebang (シバン) と同じ要領で、
https://neos21.github.io/angular-utilities/text-converter/case-converter
といった URL ではなく、
https://neos21.github.io/angular-utilities/#/text-converter/case-converter
といった URL を利用する。
全てのページは /angular-utilities/
というルート URL のハッシュとして扱われるので、この URL をコピペして遷移されても、トップページが踏まれて、Angular によって上手いことリダイレクト的な処理をやってもらえる、というワケだ。
HashLocationStrategy を利用するのは簡単で、AppRoutingModule
にて、useHash
というフラグを与えてやるだけで良い。
@NgModule({
imports: [RouterModule.forRoot(routes, { useHash: true })], // ← 第2引数で指定
exports: [RouterModule]
})
export class AppRoutingModule { }
コレなら手軽ではあるが、URL に余計な #/
が挟まるのが嫌かも…?
以上。