はじめに
Reactのビルド成果物をSpringBootの静的ファイルとして配置したら、アプリの画面リロード時にWhitelabel Error Pageが返ってきたので、解決法のメモです。
より良い解決策を望んでいるので、気になる点があればコメントいただけますと幸いです!
結論
サーバへリクエストされたパスが存在しないときにはindex.htmlを返してあげましょう。
状況整理
結論は上述した通りですが、とりあえず現状を整理します。
1.SpringBootアプリにアクセスしてみる
想定通りlocalhost:8081で起動しているアプリのトップページにアクセスすることが出来ました。

2.画面遷移をしてみる
「記録する」ボタンを押下すると、想定通り、localhost:8081/memoに画面遷移しました。

3.リロードしてみる
①localhost:8081/memoでリロードする
あ、、、Whitelabel Error Pageが出てしまいました。

②localhost:8081でリロードする
あれ、こちらは問題ないようです。

原因
SPAとは
まず前提として、Reactはシングルページアプリケーション(SPA)です。
ページの初回読み込み時に必要なデータやリソース(html,css,jsなど)を一度に取得し、その後はサーバとのやり取りを最小限に抑えて動的なコンテンツの表示や操作を行うもの
SPAのルーティングの挙動
SPAであるReactではボタンのクリックなどのユーザ操作でページ遷移などするたびにサーバへリクエストを行うことはありません。
代わりにreact-routerなどのルーティングライブラリがアドレスバー上のURLを書き換え、それに対応して、最初に取得したリソースをもとにUIを変化させています。
リロード時の挙動
ですが、リロードを行った場合は実際にサーバへリクエストが実行されることになります。
先ほどの例だと、localhost:8081/memoでリロードをおこなうと、そのURLに向けてリクエストが行われます。
リクエストが実行された結果、SpringBootは/memoというパスのリクエストを解決しようと試みます。
しかし、そのようなパスは定義されていないので、リクエストされたリソースを返却することはできません。
では、画面をリロードしたことでSpringBootに存在しないパスがリクエストされてしまった場合に、適切な画面を返却してもらうためにはどうすればよいのでしょうか。
解決
①localhost:8081/memoでのリロードについて
解決策
冒頭でも申し上げたように 「リクエストされたパスが存在しないときにはindex.htmlを返却しよう」 です。
ReactはSPAなので、index.htmlのみで全ての画面を管理しています。
実際にReactのビルド成果物を配置した/resources配下にはindex.htmlが存在しており、リロード時はこのファイルを再度返却してあげることで404エラーを回避することが出来ます。

Controllerの作成
具体的には、静的ファイルを除くパスをindex.htmlにフォワードするコントローラを作成します。
/**
* SPAのルーティングを適切化するためのコントローラ
*/
@Controller
public class SpaController {
// 静的ファイルを除く全てのパスをindex.htmlにフォワードする
@GetMapping(value = "/**/{path:[^\\.]*}")
public String forward() {
return "forward:/index.html";
}
}
マッピングされるパスに正規表現を用いて、静的ファイルの場合に含まれる「.」ドットを除いたパスを指定しています。
静的ファイルを除外しないと、cssファイルやjsファイルされるべき場合でもindex.htmlが返却され、MIME Typeエラーが発生します。
Refused to execute script because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled
注意点
通常の設定だとおそらく上記のパターンマッチングではエラーが出るため、エラーが出る場合はapplication.yamlにて以下の設定を行いましょう。
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
理由
パスの最後以外でワイルドカードを用いることが、AntPathMatcherでは許容されているが、デフォルトのPathPatternParserでは許容されていないためです。
これにてリロード時の404エラー問題は解決になります。
②localhost:8081でのリロードについて
では、localhost:8081でリロードを行った際に404エラーとならなかった理由はなんでしょうか?
これはSpringBootの機能として、インデックスルート(/)へのアクセスで何もリソースが見つからなかった場合、フォールバックとしてindex.html(またはindexテンプレート)を返却することになっているからです。
Spring Boot は、静的なウェルカムページとテンプレート化されたウェルカムページの両方をサポートしています。最初に、構成された静的コンテンツの場所で index.html ファイルを探します。見つからない場合は、index テンプレートを探します。どちらかが見つかった場合、アプリケーションのウェルカムページとして自動的に使用されます。
これは、アプリケーションによって定義された実際のインデックスルートのフォールバックとしてのみ機能します。
おわり
この構成は、Spring Boot を API サーバー兼、静的ファイルのホスティングサーバーとして利用する際に広く紹介されてきたパターンです。フロントとバックを1つのプロジェクト(1つのJAR)で管理したい場合にはシンプルな解決策な気がします。
より良い解決策があれば、コメントお願いします!!!(お願いします)
