LoginSignup
34
23

More than 5 years have passed since last update.

Angular で作った Web サイトをリロードすると 404 (Not Found) エラーになる時の対策

Last updated at Posted at 2019-01-09

概要

Angular 6 で作った Web サイト、サーバにデプロイして外部からアクセスし、リロードボタンを押したら「Not Found」と 404 エラーが返ってきた時に、どう対応すればいいかの備忘録。
※ ごく簡単な対応のため。全ての対策が書かれているわけではありません。より良い対策があればそちらを共有ください。

事象

Angular6 でサクッと構築した Web アプリを、GCPGCE で立てたサーバにデプロイした。

http://独自ドメイン/ でアプリのトップページに飛びページリロードしたら「Not Found」と表示されて画面遷移できなくなった。(もちろん、トップページに遷移し直せば、通常通り使用できる。)
また、リロードだけでなく、URLのルート以降のパスを追加・変更し Enter キーを押した時も「Not Found」になる。

しかし、ローカルで ng serve で動かしているときは、リロードしても同じ事象は発生しない。。。

原因

結論から言えば、Angular で作った Web アプリは、ルートにしか index.html が存在しないため。

つまり、ルート以外のパスを直接指定されても、そんな HTML ファイルは存在しないので、存在しない Web ページをリクエストされたら、当然 404 (Not Found) となる。

Angular は、トップページの時点で、ルート以降に何かしらのパスが存在しているはずである(/home など)。
この状態でページリロードしたときは、 前述の理由通り、HTML がそのパスには実在しないので、「Not Found」となる。URLを直指定した場合も同様の理由である。

そもそも Angular はルートに index.html しかないのに、なぜURLが変わるのか?

Angular がデフォルトで HTML5 の「History API」という機能をしようしているためである。

そもそも、History API とは、以下の様な機能を持った API である。

  • 画面を遷移せず、履歴に新たなURLを発行・追加する。
  • 現在のページの履歴を変更する。
  • ブラウザの戻る・進むボタンをクリックしたときにイベントを検知する。

Angular は、表示するコンポーネントを切り替えながら、この History API を使ってURLを変更しているのだけなので、実際に HTML を移動しているわけではない。

ちなみに、構成によるが、通常の構成であれば、React や Vue でも同様の事象は発生する。

対処法

対策1: URL にハッシュ(#)を導入する(アプリ側での対応)

ハッシュ付きURLを有効にする。
有効化すると、アドレスが http://独自ドメイン/#/homehttp://独自ドメイン/#/home の様に変化する。

Angular の場合、Angular Router の Use Hash オプションを有効にするだけ。

app-routing.module.ts
@NgModule({
  imports: [RouterModule.forRoot(routes, {
    useHash: true, // 追加
  })],
  exports: [RouterModule]
})
export class AppRoutingModule { }

対策2: Web サーバのルート修正で対応する(Web サーバ側での対応)

単純な catch-all フォールバックのためのルートをサーバー側で追加し、もし URL がどの静的なアセットにもマッチしなかった時は、アプリケーションが動作しているのと同じ index.html ページで受け付けるように設定あを行うだけ。

ng serve でリロードしても大丈夫なのは、ng serve 実行時にこの方式が採用されているから。

各 Web サービスの設定変更内容は以下のとおり。

Apache 設定
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /index.html [L]
</IfModule>
Nginx 設定
location / {
  try_files $uri $uri/ /index.html;
}
Native Node.js 設定
const http = require('http')
const fs = require('fs')
const httpPort = 80

http.createServer((req, res) => {
  fs.readFile('index.htm', 'utf-8', (err, content) => {
    if (err) {
      console.log('We cannot open "index.htm" file.')
    }

    res.writeHead(200, {
      'Content-Type': 'text/html; charset=utf-8'
    })

    res.end(content)
  })
}).listen(httpPort, () => {
  console.log('Server listening on: http://localhost:%s', httpPort)
})
Firebase ホスティング

以下を、firebase.json に追加。

{
  "hosting": {
    "public": "dist",
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

対策3: SSR を導入する

日本語公式ドキュメントのサーバサイドレンダリングの記事 などを参考に Universal にする。

以上。

34
23
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
34
23