リバースプロキシ構成での「意図しない404」と「リダイレクトループ」の原因を探る
はじめに
XServerとNext.jsを組み合わせてアプリケーションを構築した際、「意図しない404」と「リダイレクトループ」との出会いがありました。
せっかくなので、こうした挙動の背景にある仕組みと、そのトリガーとなった各種設定について調べた結果を共有します。
背景
XServerが提供してくれるウェブサーバー(Apache)には以下のような機能があります。
-
ディレクトリへのアクセス時、どのファイルを返すか(いわゆるディレクトリインデックス)を補完する
-
デフォルトは
DirectoryIndex index.html
-
私の環境では
デフォルト値
を使用
-
例えば、https://example.com/
へのアクセスは内部的にhttps://example.com/index.html
に補完されます。
同様に、https://example.com/example01/
はhttps://example.com/example01/index.html
となります。
-
URL末尾のスラッシュ(いわゆるトレイリングスラッシュ)を補完する
-
デフォルトは
DirectorySlash On
-
私の環境では
デフォルト値
を使用
-
例えば、https://example.com/example01
へのアクセスは内部的にhttps://example.com/example01/
に補完されます。
同様に、https://example.com/example01/something
はhttps://example.com/example01/something/
となります。
一方、Next.jsにも同様の機能があります。
-
App RouterとPages Routerというファイルシステムベースのルーティングを提供する
- 私の環境では
App Router
を使用
- 私の環境では
例えば、app/page.tsx
は/
にマッピングされます。
同様に、app/example01/page.tsx
は/example01
となります。
-
-
デフォルトは
trailingSlash: false
-
私の環境では
デフォルト値
を使用
-
例えば、/example01/
は/example01
に補完されます。
同様に、/example01/something/
は/example01/something
となります。
個々の機能は非常に便利で魅力的ですが、これらが衝突した際に期待したページが表示されなかったり、リダイレクトがループするのです。
では、実際に起きた2つのケースの条件と症状、そして私が行った対策を共有します。
ケース①:意図しない404
条件
-
Apache
-
RewriteRule
により、Next.jsへプロキシ転送される -
DirectoryIndex
が有効(またはデフォルト)で、index.html
が指定されている -
public_html/example01/
など、実ディレクトリが存在する -
上記のような実ディレクトリ内に
index.html
が存在しない
-
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
RewriteRule ^(.*)$ http://localhost:3000/$1 [P,L,QSA]
public_html/
└── example01/
└── (index.htmlは存在しない)
-
Next.js
-
アプリケーションが起動されている
-
/example01/index.html/page.tsx
など、Apacheの実ディレクトリと対応するindex.html
というルートが存在しない
-
project/
├── app/
│ ├── example01/
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── package.json
└── next.config.js
症状
-
https://example.com/example01
にアクセスする -
Apacheの
DirectorySlash
により、https://example.com/example01/
に変換される -
Apacheの
DirectoryIndex
により、/example01/index.html
が内部的にリクエストされる -
そのリクエストがプロキシを通じてNext.js側へ転送される(例:
http://localhost:3000/example01/index.html
) -
Next.js側には
/example01/index.html
に対応するルートが存在しないため、404エラーが返される
※ https://example.com
へのアクセスでも同様のことが起きます
対策
-
DirectoryIndex disabled
を指定して補完を抑止する -
RewriteRule
でディレクトリアクセスを明示的に処理する -
ルーティングと競合しうる不要な実ディレクトリは削除する
RewriteEngine On
DirectoryIndex disabled
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
RewriteRule ^index\.html$ / [R=301,L]
RewriteRule ^(.*)$ http://localhost:3000/$1 [P,L,QSA]
ケース②:リダイレクトループ
条件
-
Apache
-
RewriteRule
により、Next.jsへプロキシ転送される -
DirectorySlash
が有効(またはデフォルト)となっている -
DirectoryIndex disabled
に設定されている -
public_html/example02/
など、実ディレクトリが存在する
-
-
Next.js
-
アプリケーションが起動されている
-
trailingSlash: false
(またはデフォルト)が指定されている
-
症状
-
https://example.com/example02
にアクセスする -
Apacheの
DirectorySlash
により、https://example.com/example02/
に変換される -
そのリクエストがプロキシを通じてNext.js側へ転送される(例:
http://localhost:3000/example02/
) -
Next.jsの
trailingSlash
により、スラッシュなしのURL(/example02
)へリダイレクトされる -
https://example.com/example02
にアクセスする -
以下ループ
対策
-
ApacheとNext.jsでスラッシュポリシーを揃える
-
実ディレクトリとルーティングの競合を避ける設計にする
-
ルーティングと競合しうる不要な実ディレクトリは削除する
おわりに
今回の出会いを通して、ウェブサーバーとアプリケーションの間での「ルーティングの役割分担」について、改めて理解が曖昧だったと気づかされました。
XServerとNext.jsのリバースプロキシ構成だけでなく、こういったディレクトリインデックスやトレイリングスラッシュの処理の競合は、任意のウェブサーバーと任意のアプリケーションの構成であれば起こり得ることだろうと思います。
解決までに時間はかかりましたが、とても良い経験になりました。
同じような構成でつまずいた方の助けになれば幸いです。
Discussion