8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【SpringBoot】Reactを統合した際のリロード404エラー対応メモ

8
Posted at

はじめに

Reactのビルド成果物をSpringBootの静的ファイルとして配置したら、アプリの画面リロード時にWhitelabel Error Pageが返ってきたので、解決法のメモです。
より良い解決策を望んでいるので、気になる点があればコメントいただけますと幸いです!

結論

サーバへリクエストされたパスが存在しないときにはindex.htmlを返してあげましょう。

状況整理

結論は上述した通りですが、とりあえず現状を整理します。

1.SpringBootアプリにアクセスしてみる

想定通りlocalhost:8081で起動しているアプリのトップページにアクセスすることが出来ました。
image.png

2.画面遷移をしてみる

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

3.リロードしてみる

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

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

原因

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エラーを回避することが出来ます。
image.png

Controllerの作成

具体的には、静的ファイルを除くパスをindex.htmlにフォワードするコントローラを作成します。

SpaController.java
/**
 * 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にて以下の設定を行いましょう。

application.yaml
spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher

理由

パスの最後以外でワイルドカードを用いることが、AntPathMatcherでは許容されているが、デフォルトのPathPatternParserでは許容されていないためです。

これにてリロード時の404エラー問題は解決になります。

ルーティングにも存在しないパスが入力された場合
レスポンスのステータスコードが200となります。
サーバとして、Reactアプリを起動するのに必要なindex.htmlを返却できた、と考えればステータスコードが200でも違和感はないですね。
URLが存在するか判断して404エラーの画面を返却するのはフロント側で行えばよいでしょう。
image.png

localhost:8081でのリロードについて

では、localhost:8081でリロードを行った際に404エラーとならなかった理由はなんでしょうか?
これはSpringBootの機能として、インデックスルート(/)へのアクセスで何もリソースが見つからなかった場合、フォールバックとしてindex.html(またはindexテンプレート)を返却することになっているからです。

Spring Boot は、静的なウェルカムページとテンプレート化されたウェルカムページの両方をサポートしています。最初に、構成された静的コンテンツの場所で index.html ファイルを探します。見つからない場合は、index テンプレートを探します。どちらかが見つかった場合、アプリケーションのウェルカムページとして自動的に使用されます。
これは、アプリケーションによって定義された実際のインデックスルートのフォールバックとしてのみ機能します。

おわり

この構成は、Spring Boot を API サーバー兼、静的ファイルのホスティングサーバーとして利用する際に広く紹介されてきたパターンです。フロントとバックを1つのプロジェクト(1つのJAR)で管理したい場合にはシンプルな解決策な気がします。
より良い解決策があれば、コメントお願いします!!!(お願いします)

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?