VueRouter 404エラー対策

# VueRouterを使ったデモアプリを作成

[フロントはVue.js、バック（RESTAPI）はSpringBootのデモアプリを作りました](https://qiita.com/buto/items/9e1fefb191edc8f31b6f)
アプリケーションはjar実行しています（SpringBoot組み込みTomcatを利用）

トップページのBLOGボタン押下でブログページを表示します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530205/813604f7-c861-a5ee-8bc9-715a21790377.png)
ブログページはSpringBootで作成したRESTAPIのデータを表示しています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530205/4afafced-2730-c5c6-8563-6243ac26dec2.png)
ブログページに表示している記事はRESTAPIで取得しています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530205/3694a594-0d12-f0ad-80f2-8b14d3874519.png)

# ページをリロードしようとすると404エラーが発生

ブログページでリロードしようとすると404エラーで白い画面になってしまいます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530205/f2ebf395-f09b-e19c-d0d0-352719f934cd.png)

この事象はVueRouterのリファレンスでも以下のように説明されています
[VueRouterガイド-HTML5 History モード](https://router.vuejs.org/ja/guide/essentials/history-mode.html#html5-history-%E3%83%A2%E3%83%BC%E3%83%89)
>しかしながら一点問題があります。シングルページのクライアントサイドアプリケーションなので、適切なサーバーの設定をしないと、ユーザーがブラウザで直接 http://oursite.com/user/id にアクセスした場合に 404 エラーが発生します。

# router.jsでワイルドカードを指定するだけではダメだった

404エラー時に表示したいエラーページを作成して、router.jsにルーティング設定をしただけでは解消されませんでした
<details><summary>ErrorPage.vue</summary><div>

```demo\web\src\pages\ErrorPage.vue
<template>
  <v-app>
    <v-app-bar
        absolute
        color="red lighten-1"
        dark
    >
      <v-toolbar-title>エラーが発生しました</v-toolbar-title>
    </v-app-bar>
    <v-container class="ml-0 mt-16">
      <v-row>
        <v-col cols="12">
          トップページに戻ってください
        </v-col>
      </v-row>
      <v-row>
        <v-col cols="4">
          <v-btn
              color="blue-grey lighten-4"
              width="100"
              v-on:click="clickTopPageBtn"
          >
            top page
          </v-btn>
        </v-col>
      </v-row>
    </v-container>
  </v-app>
</template>

<script>
export default {
  name: "ErrorPage",
  methods: {
    clickTopPageBtn: function () {
      this.$router.push("/")
    }
  }
}
</script>

<style scoped>

</style>
```

</div></details>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530205/f0bd6232-bb52-7a21-9866-9f1e08add914.png)

```demo\web\src\router.js
import Vue from "vue"
import Router from "vue-router"

import TopPage from "@/pages/TopPage";
import BlogPage from "@/pages/BlogPage";
import ErrorPage from "@/pages/ErrorPage"

Vue.use(Router)

export default new Router({
    mode: "history",
    routes: [
        {
            path: "/",
            name: "トップページ",
            component: TopPage
        },
        {
            path: "/blogPage",
            name: "ブログ",
            component: BlogPage
        },
        {
            path: "*",
            name: "エラーページ",
            component: ErrorPage
        }
    ]
})
```

# Apache .htaccessで解決

Windows用のApacheを導入しました（VueRouter公式ガイドにもWebServerを使えと書いてありましたね…）
Apache+SpringBootJar構成でアプリが動くように設定していきます

### AJP通信の設定
今回のアプリはブログページでVue.jsの画面からSpringBootのAPIを呼び出すので
AJP通信を利用してフロントとバックを繋ぎます
#### ApacheのAJP通信設定

Apacheにはビルド済みVue.jsを配置します
8009ポートでAJP通信できるようにhttpd-proxy-ajp.confを作成します

```C:\httpd-2.4.46-o111h-x86-vc15\Apache24\conf\extra\httpd-proxy-ajp.conf
<Location /blog>
  ProxyPass ajp://127.0.0.1:8009/blog
</Location>
```

httpd.confでもAJP通信ができるよう修正します

```C:\httpd-2.4.46-o111h-x86-vc15\Apache24\conf\httpd.conf
# 以下のモジュール読み込み箇所のコメントを解除
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
# 末尾に先ほど作成したhttpd-proxy-ajp.confを読み込む設定
Include conf/extra/httpd-proxy-ajp.conf
```
#### SpringBootのAJP通信設定

SpringBootの組み込みTomcatでApacheとAJP通信する設定クラスを作成します

```demo\src\main\java\com\example\demo\config\AppConfig.java
package com.example.demo.config;

import org.apache.catalina.connector.Connector;
import org.apache.coyote.ajp.AbstractAjpProtocol;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public TomcatServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
        Connector ajpConnector = new Connector("AJP/1.3");
        ajpConnector.setPort(8009);
        ajpConnector.setSecure(false);
        ajpConnector.setAllowTrace(false);
        ajpConnector.setScheme("http");
        ((AbstractAjpProtocol) ajpConnector.getProtocolHandler()).setSecretRequired(false);
        tomcat.addAdditionalTomcatConnectors(ajpConnector);

        return tomcat;
    }
}

```

### .htaccess対応

.htaccessにはリダイレクトの設定を記述します
Apacheのドキュメントルートに配置しただけだと.htaccessが適用されません

#### httpd.confの.htaccess適用設定

httpd.confを以下のように変更します

```C:\httpd-2.4.46-o111h-x86-vc15\Apache24\conf\httpd.conf
# 以下のモジュール読み込み箇所のコメントを解除
LoadModule rewrite_module modules/mod_rewrite.so
# ドキュメントルートのAllowOverride NoneをAllに変更
DocumentRoot "${SRVROOT}/htdocs"
<Directory "${SRVROOT}/htdocs">
    Options Indexes FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>
```

また、Apacheを配置しているディレクトリにmod_rewrite.soファイルが存在することを確認します
私の環境では`C:\httpd-2.4.46-o111h-x86-vc15\Apache24\modules\mod_rewrite.so`でした

#### .htaccessを作成

VueRouter公式ガイドに記載されている設定内容をそのまま使っています

```C:\httpd-2.4.46-o111h-x86-vc15\Apache24\htdocs\.htaccess
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /index.html [L]
</IfModule>
```
このファイルをApacheのドキュメントルートに配置します
私は`C:\httpd-2.4.46-o111h-x86-vc15\Apache24\htdocs\`に配置しました

これでApacheとアプリケーションの設定は完了です

### ApacheにVue.jsリソースを配置

いつも通り`npm run build`を実行すればOKです

```
cd C:\Users\risab\git\demo\web
npm run build
```
package.jsonで特に指定していなければビルド済みリソースは\dist配下に出力されます

出力されたビルド済みリソースをApacheドキュメントルートに配置します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530205/9987650a-de5d-eaae-f284-aab072a05f03.png)

リソースを配置したら、WindowsのサービスからApacheサービスを起動しておきます
Apacheサービス登録は以下のコマンドでできます

```
cd C:\httpd-2.4.46-o111h-x86-vc15\Apache24\bin
httpd -k install
```

### SpringBootJarを生成
[詳細な手順はこちら](https://qiita.com/buto/items/9e1fefb191edc8f31b6f#jar%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B)　gradleでjarを生成します

```
cd C:\Users\risab\git\demo
gradlew bootJar
```
jarが生成されたら以下のコマンドでアプリケーションを起動します

```
cd C:\Users\risab\git\demo\build\libs
java -jar demo-0.0.1-SNAPSHOT
```

### これで対策完了！

Apacheにフロントリソースを配置しているので`http://localhost/`にアクセスします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530205/b600a456-ff52-c188-f18a-c2b764d6b525.png)
VueRouterでページ遷移、AJP通信で画面からバックのAPIも呼び出せています
画面のリロード時も404エラーが発生せず、リロードできるようになっています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530205/d49efbe8-0f15-edda-547d-fde52f563f86.png)
また、存在しないURLにアクセスした場合は.htaccessとVueRouterのワイルドカードが適用されて
エラーページが表示されるようになりました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530205/abbbc281-48d5-013f-538d-41b88bf72b19.png)