Vue.jsのhistory mode 404対策について

# はじめに

vue.js で初めてシングル・ページ・アプリケーションを作成しました。その際に vue router の history mode でつまづいたので共有とともに、適切な方法があればご教示いただければと思います。  

# 問題点

vue router では HTML5 History Mode を用いた history mode というのを設定することができます。  
history mode の詳しい情報は[公式](https://router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations)をご確認いただきたいのですが、vue-router には 2 つのモードが存在します。  
1 つが hash モードで下記のように URL に#がつきます。  
`https://xxx.com/#/tools`  
もうひとつが hitsory モードで#を取り除いた綺麗な URL にすることができます。しかし、公式にもある通り、history mode では適切な処理をしないと、ユーザが直接 URL で検索した時や、web アプリケーション閲覧中にリロードをした際に、サーバーからは 404 が返ってきてしまいます。  
こちらの 404 対策ですが、公式のやり方では 404 にはならず index.html には遷移することができますが、意図したページに遷移することができませんでした。  
例えば、  
ホーム画面: `https://domain.com/`  
ニュース画面: `https://domain.com/news`  
ツール画面: `https://domain.com/tools`  
と 3 ページ用意したとします。ユーザが直接`https://domain.com/tools` と URL を叩いた場合も、index.html に遷移しホーム画面にリダイレクトされてしまいます。本来やりたいことは`https://domain.com/tools`を入力した場合やツール画面でリロードした際はホーム画面ではなくツール画面に遷移することです。  

# 解決方法

## 環境

_Frontend_ : Vue.js  
_Backend_ : Spring boot(Restful API を作成)  
_アプリケーション・サーバー_ : HTTP Server

## アプリケーション・サーバーの設定

アプリケーション・サーバーの httpd.conf ファイルに下記を入力します。  

```conf:httpd.conf
RewriteEngine On
    RewriteRule ^/"任意のコンテキストパス"/$ - [L]
    RewriteRule ^/"任意のコンテキストパス"/((?!.*(\..+)$).*)$ https://domain.com/"任意のコンテキストパス"/?redirect_path=$1
```  

こちらのリダイレクト設定が意味するのは
`https://domain.com/"任意のコンテキストパス/"`に一致する URL がリクエストされた場合はそのまま`https://domain.com/"任意のコンテキストパス/`をレスポンスし、 `https://domain.com/"任意のコンテキストパス/hoge`がリクエストされた場合は`https://domain.com/"任意のコンテキストパス/?redirect_path=hoge`がレスポンスされるようにします。  
3 行目の`((?!.*(\..+)$).*)`は開発者が用意した css や js ファイルまでリダイレクトされるのを防いでいます。

## Frontend の設定

router.ts に下記のように設定します。  

```typescript:router.ts
import Vue from 'vue';
import Router from 'vue-router';
import { authorize } from '@/router/guard';

Vue.use(Router);

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: () =>
        import('@/components/templates/Home.vue'),
      beforeEnter: authorize,
    },
    {
      path: '/hoge',
      name: 'hoge',
      component: () =>
        import('@/components/templates/Hoge.vue'),
      beforeEnter: authorize,
    },
    {
      path: '/fuga',
      name: 'fuga',
      component: () =>
        import('@/components/templates/Fuga.vue'),
      beforeEnter: authorize,
    },
    {
      path: '/*', // 該当するページが存在しない場合は404ページに遷移する
      name: 'notfound',
      component: () =>
        import('@/components/templates/NotFound.vue'),
      beforeEnter: authorize,
    },
  ],
});
```  

そして guard.ts を作成し、下記のように記述します。  

```typescript:guard.ts
import store from '@/store';

export const authorize = (to: any, from: any, next: any) => {
  const queryKey = 'redirect_path';
  if (to.query[queryKey]) { // パスが存在している場合(RewriteRule ^/"任意のコンテキストパス"/((?!.*(\..+)$).*)$ https://domain.com/"任意のコンテキストパス"/?redirect_path=$1 の条件に該当する時)
    next({
      path: `/${to.query[queryKey]}`, // 取得したパスにvue-routerで遷移する
    });
  } else { // パスが存在しない場合(RewriteRule ^/"任意のコンテキストパス"/$ - [L] の条件に該当する時)
    // 任意の処理を記述
  }
};

```  

## 結果

これらの設定を行うことで、例えば`https://domain.com/contextpath/hoge`とリクエストを送ると、サーバーからは`https://domain.com/contextpath/?redirect_path=hoge`とレスポンスされ、フロントエンド側では`redirect_path=hoge`の hoge を取得し、hoge のページにルーティングします。仮に`https://domain.com/contextpath/hogehoge`のように用意されていないページがリクエストされたときは、フロント側で 404 のページに遷移するように今回の例ではFrontend側で設定しています。

# まとめ

ほかの vue で作成されたページを観察すると、このようなリダイレクトをしているように見えないのですが、他の方はどのように設定しているのでしょうか？？