Node.js
nginx
Express
AngularJS

サブディレクトリ URL で起動する Angular の Web アプリで API 通信を実装したらハマった話+解消手順


概要

Angular (+ express) + Node.js でバックエンドに API 通信をさせた時にエラーでハマった経緯と、その解消策の備忘録


先に結論から言うと

「Nginx の設定をミスったら、バックエンド通信に失敗するよ」と言う話。


経緯

Angular + express で MEAN スタックで作成した Web アプリ。

今までは普通に、開発 ➡️ サーバへデプロイ ➡️ 実機で動作確認、で問題なく動いていた。

ただし、ドメイン取得して、サーバのトップレベルで動作する Web サイトとして作っていた。

今回は、サブディレクトリでのみ動作する Web サイトとして開発。

つまり...



  • https://example.com ▶︎ 別の Web サイト(静的 HTML ページ)


  • https://example.com/my-app ▶︎ 今回の Angular サイト

としようとした。

実際に、Angular でアプリを開発して配置。


事象と解消手順


事象1: 画面表示でエラー発生

https://example.com/my-app にアクセスしたら、テキストだけ出るけど装飾系が描画されない。と言う中途半端な状態で画面表示。表示が壊れている状態。。。

もちろん、https://example.com は問題なく規定の Web ページが表示される。


▶︎ 解消: 「base href」を設定したら解消

index.html の <base href="/"><base href="/my-app/"> となると解消する。

しかし、ローカル環境では必要ない...。

なので、以下のとおり、package.json のビルドコマンドに --base-href オプションを指定することで、ビルド時に「base href」を変え分けられるようにした。


package.json

  :

"scripts": {
// 起動コマンド
"dev": "node ./dist/server/server.js",
// ビルドコマンド (ng build に --base-href オプション追加)
"build": "npm run build:client && npm run build:server",
"build:client": "ng build --prod --base-href=/my-app/ && ng run my-app:server",
"build:server": "tsc --project server --outDir dist --allowjs true",
:
},
:



事象2: バックエンドへの通信時にエラー発生

「事象1」が解消し、画面表示はできるようになったが、

今度は、API でバックエンドにリスト取得する処理を実装したら以下のエラー事象が発生。

ローカルだと取得できてたのに、サーバだとリスト取得できない。。。(本来なら JSON のリストデータが返却される予定)

ブラウザコンソールを開いてみたら、以下のエラーログを吐いていた。

# 発生したエラー

error: SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse...
headers: t {normalizedNames: Map(0), lazyUpdate: null, lazyInit: ƒ}
message: "Http failure during parsing for https://iexample.com/portal/api/my-app/api/heros"
name: "HttpErrorResponse"
ok: false
status: 200
statusText: "OK"
url: "https://example.com/my-app/api/heros"

バックエンド通信で、HTTP 結果コードは 200 が返っているが、返却された後に何かエラーが出ている。

error の部分を開いて詳しく見てみると

SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse (<anonymous>) at XMLHttpRequest

さらに、すぐ下に

text: "<!DOCTYPE html><html lang="ja"><head>↵  <meta charset="utf-8">↵  <title>My App</title>↵ 

JSON データ、もしくは、 JSON のリストデータが返却されるべき箇所で、HTML を返しているために、JSON.parse() でエラーが発生したことが原因らしい。

長く悩んだが、よく考えたらそりゃそうだ。Web サービスで画面用のルーティングしか設定してなかった。


▶︎ 解消: Nginx のルーティングに API 専用のルートを追加したら解消


/etc/nginx/conf.d/default.conf

server {

# http を https にリダイレクト
listen 80;
return 301 https://$host$request_uri;
}

server {
listen 443 ssl;
server_name localhost;

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# *** 追加: 以下の API 用ルーティングを追加 ↓↓↓ ******
location /my-app/api/ {
proxy_pass http://127.0.0.1:3000/api/;
proxy_redirect default;
}
# *** 追加 ↑↑↑ *********************************

location /my-app/ {
proxy_pass http://127.0.0.1:3000/;
proxy_redirect default;
}

location / {
root /var/www;
index index.php index.html index.htm;
}

# 404, 50x 等 エラー系のルートが以下にある
}


設定後、nginx -s reload で Nginx を再起動。

Angualar も再デプロイして起動し直し、ブラウザから再度アクセスしてみたら、

エラーは出なくなり、問題なく目的の JSON リストデータが返却されて、画面に表示された。


ハマりどころ

そもそも「Angular の設定方法やロジックに原因がある」として、ずーーーーっと、アプリ設定のネット記事を漁っていたが、そこが間違いだった。


原因の切り分けフロー

HTTP 結果は 200 で、エラーの吐き方が、JSON パースエラー: クラサバ通信はできている

→ リクエストとレスポンスの機構を改めて確認: SSR で実装しているので、リクエスト/レスポンスは HTML を返却するのと JSON データを返却する2種類がある

→ ローカル環境で、ブラウザに GET メソッドの API を叩いてみる: JSON データが返り、画面に表示されることを確認

→ 公開サーバで、同じくブラウザに GET メソッドの API を叩いてみる: トップページが再表示された = HTML が返却された

→ Web サーバのリクエスト URI に対するルーティング先を修正すべきことに気づく

→ Nginx の config を修正して動作確認

問題解消 !!!

こんな感じだった。

エラーの切り分けを丁寧に行い、対応していくことは大事だ。

以上、Web サービスの設定は、大した内容ではないが間違えると結構ハマる。