Angularで作ったアプリをApacheサーバーにUPして公開する

タイトル通りです。

要は、ビルドして、サーバーにアップするってことなんですけど、たったそれだけのことにいろいろ罠があるというのがプログラミング界隈の常でして(ですよね？)。

ひっかかった罠についていろいろ書いておきます。

同じ罠にかかった人の助けになるように、そして、自分が同じ罠に二度とかからないようにね！！！！

#環境

何が重要になるかわからないのでズラズラ書いておきます。

##ローカル

 - Mac OS X El Capitan
 - Angular CLI: 1.7.3
 - Node: 8.9.4
 - OS: darwin x64
 - Angular: 5.2.9

##サーバー

 - VPS借りてます
 - CentOS Linux release 7.0.1406 (Core) 
 - Apache/2.4.6 (CentOS)


#ビルド

ビルドとは、動作させる用のファイルを生成することです。

ただのJavaScriptファイルならそのままでも動くんですが、

Angularでは、書いてるコードはそのままでは動かないのです。


##どうやってビルドするの

ターミナルで、プロジェクトのディレクトリに移動して

`ng build --prod --base-href=/greatapp/`

を実行します。

すると`dist`フォルダに、ビルド結果が出力されます。

複数のファイルが出力されますが、全部まとめてサーバーにアップすることになります。

ところで、コマンドにオプションが2つついてますね。

説明します。

###--prod

"product"から来てると思います。「製品版」ってことですね。

いろいろ最適化して「これだけあれば十分だよ！」っていう小さなファイル群を作成してくれます。ありがたい。


###--base-href=/greatapp/

例えばあなたのサーバーのアドレスが http://example.com で、これを作ったアプリケーションのアドレスにするなら、このオプションは不要です。

でももし http://example.com/greatapp をアプリケーションのアドレスにしたいなら、このオプションが必要です。

でないと、ファイル間のリンクに失敗します。

## ビルドしたファイルは直接アクセスしても動かない

せっかくビルドしたのですぐ動作確認したいですが、これ、ブラウザで「file://..../greatapp/index.html」みたいに直接アクセスしても動きません。

おとなしくサーバーにアップしましょう。

参考↓ この質問のベストアンサー**じゃない**回答の人がそう言ってます
https://teratail.com/questions/91413

#サーバーにアップロード

サーバーの公開ディレクトリ(ドキュメントルート)の中におきましょう。

レンタルサーバーの人はFTPSとかを使ってアップすればよいかな。

僕の場合はscpを使いました。

ただ、ドキュメントルートに直でアクセスしようとするとPermission Deniedで怒られたので、いったん別の場所にアップロードしてから、ルート権限でドキュメントルートにコピーすることにしました。

##一旦、仮の場所にアップ

`scp -r -P 1234 dist/ youraccount@123.45.678.9:/home/yourname/greatapp/`


`1234`はポート番号
`youraccount`はsshでログインする時に使うアカウント
`123.45.678.9`は対象サーバーのIP
`/home/yourname/greatapp`はアプリを置きたいサーバー上の場所

です。

###二回目以降は、先にこのディレクトリを消さないと上書きされなかった

理由はわかりませんが、何か修正をして、再びビルドしたファイルを同じ場所にアップしようとすると、上書きしてくれませんでした。

なので、二回目以降は先にこのディレクトリの中身を消すようにしました。

sshでサーバーにアクセスした状態で

`rm -rf /home/yourname/greatapp/`

これが最善策とはとても思えないので、良い方法を知ってる方は教えてください。。。

###ドキュメントルートって何

サーバーにアクセスした時のパスの起点になるディレクトリです。

http://example.com みたいにアクセスすると、ドキュメントルートの直下においてあるindex.htmlが表示されるのが普通ですね。

http://example.com/hoge/ なら、ドキュメントルートにあるhogeというディレクトリにあるindex.htmlが表示されることになります。

###ドキュメントルートの確認

`httpd.conf`の中に`DocumentRoot`という記述があり、そこに書いてあります。

僕の場合は`/var/www/html`でした。

###httpd.confはどこにあるの
僕の場合は`/etc/httpd/conf/httpd.conf`にありました。

##ドキュメントルートにコピー

こちらもsshでサーバーにアクセスした状態で

`\cp -rf /home/yourname/greatapp/ /var/www/html/`

これで、`/var/www/html`に`greatapp`というフォルダが作られて、そこにさっきアップロードしたものがコピーされます。

`-r`オプションで、フォルダの中身を全部コピー
`-f`オプションで、ファイル一つずついちいち確認が出るのを防ぎます

でもサーバー側の設定で、`-f`オプションがそのままだと機能しないので、頭に`\`がついてます。これでうまくいきます。


#パス関係の設定

主に`httpd.conf`に設定を記述していきます。

人によっては、`httpd.conf`ではなく`.htaccess`の方に記述する必要があるかもしれません。

##エイリアスを設定

ひょっとしたらこのエイリアスの設定、不要かもしれません。

ですが、僕の環境では必要だったので記しておきます。

同じサーバー上でDjango用のWSGIを動かしている関係かなあ？


```conf:httpd.conf
Alias /greatapp /var/www/html/greatapp
```

これで、http://example.com/greatapp のようにアクセスした時、先ほどアプリを置いた`/var/www/html/greatapp`にアクセスが行くようになります。

パスの最後に`/`は付けないようにします。それによって、ブラウザにアドレスを入力するときに最後の`/`があってもなくても正しくアクセスできるようになります。(`/`がない場合に補完されるようになる)

##更新対策

現状のままだと、Angularのルーティングが正しく機能しません。

正確には、機能するのですが、Angularの機能でアドレスバーの値が変わった後、ブラウザの更新ボタンを押したりすると、404 not found が返ってしまいます。

例えば、最初に http://example.com/greatapp にアクセスしたあと、アプリ内の何かをクリックして http://example.com/greatapp/page1 に移動したとします。

この状態で更新すると、サーバーは`/var/www/html/greatapp/page1/`を探しに行きますが、そんなものはないので、404が返るわけです。

最初から http://example.com/greatapp/page1 にアクセスしようとしても同じく404が返ります。

これを正しく機能するようにするには、

 - http://example.com/greatapp/任意の文字列 にアクセスがあったら
 - アドレスバーの値は変えないまま
 - /var/www/html/greatapp/index.html を返す

という動きをする必要があります。

で、結局こうしました。`httpd.conf`に、下記を追記します。

```conf:httpd.conf
<Directory /var/www/html/greatapp>
  <IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . index.html [L]
  </IfModule>
</Directory>
```
ここに書いてある内容については、下記サイトの説明がわかりやすいと思います。
このサイト自体はWordPressの場合を想定した説明ですが、まったく同じことです。
https://html-coding.co.jp/knowhow/tips/wp_modrewrite/

ただし、このサイトの説明にでてくる RewriteBase という行は、`httpd.conf`には書く必要がないです。`.htaccess`に書く場合の話ですね。

もし`httpd.conf`に RewriteBase を書いてしまうと、下記のエラーがでます。

`Job for httpd.service failed. See 'systemctl status httpd.service' and 'journalctl -xn' for details.`

言われたとおり`journalctl -xn`をターミナルから実行すると

`RewriteBase: only valid in per-directory config files`

という記述が見つかります。`per-directory config files`というのが`.htaccess`のことだと思います。もし違ったら教えてください。

##存在しないパス(アドレス)への対応はAngularアプリ側で設定

ここまでの設定だけだと、設定していない適当な文字列を指定して`http://example.com/greatapp/hogefuga`みたいなアドレスにアクセスした時に

アドレスバーの値はそのままで、アプリが表示されることになります。

アプリ側のルーティング設定に`hogefuga`がない場合、`<router-outlet></router-outlet>`の部分に何も表示されないことになってしまいますね。

これではいけないのでアプリ側の設定が必要です。

どれにもマッチしないテキトーな文字列はすべてトップページにリダイレクトしてしまいましょう。

```typescript:/src/app/app-routing.module.ts
const routes: Routes = [
  //この辺にパスの設定がいろいろ書いてあって、最後に↓
  { path: '**', redirectTo: '/' }
];
```
404用のコンポーネントを作ってそこにルーティングする手もありますね。

そして、どちらにしても、あとからこの設定を追加したなら、またビルドしてサーバーにアップする必要があります。

先にやってあった人は大丈夫です。

#サーバーを再起動

もちろんsshにアクセスした状態で

`apachectl restart`

`apachectl graceful`の方が良いという話もあります。

#アクセス！！

とりあえずこれでうまくいきますが、「普通はそうはやらない」とか、バッドノウハウが含まれてたら教えてください。

ところで、この辺設定を変えながら色々試してるとき、いきなりhttpd.confの設定変更が何も反映されなくなったことがありました。サーバーの再起動もきちんとしているのに、です。

原因は、ブラウザのキャッシュでした。ありがちな話ですね。

試行錯誤しながらサーバーの設定をいじる際は、ブラウザのキャッシュも毎回消さないと、何がどう変わったのかきちんと検証できませんので気をつけて下さい。

#そうしてアップしたものがこちら

ちなみに、今回の方法で僕がアップして動かしているものがこちらになります。

[フリックとローマジ -- 日本語入力スピード測定](http://oka-ryunoske.work/flickromaji/)

スマホのフリック入力だろうがパソコンのローマ字入力だろうが、同じ条件で入力速度を測定できるWebサービスです。

よかったら遊んでみて下さい！で、何点とれたかSNSで報告してください！お待ちしてます！