React Router DOMとGitHub Pageで、404 Not Foundをなくす方法

# はじめに
React Router DOMって、便利ですよね。
でも、直接URLにアクセスしたとき、
ただの静的ホスティングだと404 Not Foundになってしまいます。

そこで、今回は無料で使える静的ホスティングサービスGitHub PagesとReact Router DOMを組み合わせて、404が起こらないような構成を作ってみたいと思います。（無理やり）

多分、もっといい方法が存在すると思うので、その時はコメントで教えてくださいますと嬉しいです。

# GitHub Pagesの仕様
GitHubのレポジトリにあるファイルをそのまま静的ホスティングしてくれるというものです。

GitHub Pagesに、パスが見つからなかったら。
`/404.html`に飛んでくれるという仕様があります。
（カスタム404ページ作成機能）

これを利用します。

# React Router DOMの仕様
React Router DOMとは、Reactのライブラリで、
一つのページを使って複数のページを再現するというものです。

### 仕組み
サイト内のどこかに飛ぼうとすると、JavaScriptが、
再読み込みさせずに、ブラウザに表示されるアドレスを変更し、
表示される内容を入れ替えています。
まるで、違うページに飛んだように見えます。

### 利点
- ページ遷移が非常に早い
- 静的ホスティングでも、動的なRoutingを作ることができる。

::: note
fetchをすることなく、JavaScriptで、表示内容を入れ替えているだけなので、ページ遷移がとても早い。
:::

### 問題点
- 直接URLを入力して、ルート以外のページを見ようとすると、
あくまでも、React Router DOMによる模擬的なページであり、
404 Not Foundが発生する。
- ページに個別のheadを設定できないので、SEO対策がしにくい

### したいこと
1つ目の問題点が、致命的すぎたので、無理やり回避しようと思う。

# 原理

`/`にアクセスされたとき
　→そのままReactを動かしてページを表示する（普通）

 他にアクセスされたとき
 　→404.htmlが読み込まれるので、現在のパスをURLパラメータに保存した状態で、`/`に飛ばす。
　→`/`のページが、パラメーターを読み取り、Reactに渡して、模擬的にページ遷移
<br>

![フローチャート図.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3882850/de7723cd-bdf3-fd19-e400-5a413c6c8bd1.jpeg)

# 実装例
404.htmlは、React projectの`/public`に保存して、静的ホスティングしました。

``` 404.html
<!doctype html>
<html lang="ja">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="loader.css">
</head>

<body>
    <!--簡単なローディング画面-->
    <div id="loading">
        <div class="loading-wrapper">
            <div class="loader"></div>
        </div>
        <div class="title"><span></span></div>
    </div>
</body>
<script>
    // 現在のパスを取得
    const path = window.location.pathname;

    // パラメーター付きのルートページに遷移する
    window.location = `/?path=${path}`;
</script>
</html>
```
`window.location.pathname`で、
例えば、
`https://*****.com/your/page/url`なら、
`/your/page/url`を取得できます。

`window.location = "TARGETPATH"`で、普通のページ遷移を起こせます。


<br>

↓React Projectのルートに保存する。
``` index.html
<!doctype html>
<html lang="ja">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>

<body>
  <div id="root" style="display: none;"></div>
  <script type="module" src="/src/main.tsx"></script>
  <div id="loading">
    <div class="loading-wrapper">
      <div class="loader"></div>
    </div>
    <div class="title"><span></span></div>
  </div>
</body>
<script>
  // ロードが終わったらローディング画面を消して、Reactのルートを表示する
  window.onload = () => {
    document.getElementById("root").style.display = "block";
    document.getElementById("loading").style.display = "none";
  };
</script>
</html>
```
<br>

以下のようなカスタムフックを作成します。
```Home.tsx
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";

function useRedirect() {
    const [isRedirected, setIsRedirected] = useState<boolean>(false);
    const navigate = useNavigate();
    
    useEffect(() => {
        const url = window.href;
        const urlObj = new URL(url);
        const params = new URLSearchParams(urlObj.search);
        const targetPath = params.get("path");
        if (targetPath != null) {
            // ページ遷移
            setIsRedirected(true);
            navigate(targetPath);
        }
    }, [navigate]);

    
    return isRedirected;
}

export default useRedirect;
```
このフックをルートのページのページコンポーネントに入れておくとうまい具合にReact内でリダイレクトしてくれるはずです。

## 終わりに
結構、便利だと思います。
ただ、一度ルートページのレンダリングをReactが行うので、
そこが無駄になっています。

僕には、思いつきませんでした。

いつも使っている開発環境が、ネイティブなものじゃなかったので、
本番環境をあまり再現できず、デバッグに時間がかかりました。

このアイデアを、
このサイトを作る上で実際に使っています。
GitHubの方、ぜひご覧ください。

https://www.sonneko.com

<br>
ソースは、↓

https://github.com/sonneko/sonneko.github.io

#### 今回のコードを使ってるところ
- `/public/404.html`
- `/src/pages/Home.tsx`
- `/index.html`