⚔️

リバースプロキシ構成での「意図しない404」と「リダイレクトループ」の原因を探る

に公開

はじめに

XServerとNext.jsを組み合わせてアプリケーションを構築した際、「意図しない404」と「リダイレクトループ」との出会いがありました。

せっかくなので、こうした挙動の背景にある仕組みと、そのトリガーとなった各種設定について調べた結果を共有します。

背景

XServerが提供してくれるウェブサーバー(Apache)には以下のような機能があります。

例えば、https://example.com/へのアクセスは内部的にhttps://example.com/index.htmlに補完されます。
同様に、https://example.com/example01/https://example.com/example01/index.htmlとなります。

例えば、https://example.com/example01へのアクセスは内部的にhttps://example.com/example01/に補完されます。
同様に、https://example.com/example01/somethinghttps://example.com/example01/something/となります。

一方、Next.jsにも同様の機能があります。

  • App RouterPages Routerというファイルシステムベースのルーティングを提供する

    • 私の環境ではApp Routerを使用

例えば、app/page.tsx/にマッピングされます。
同様に、app/example01/page.tsx/example01となります。

例えば、/example01//example01に補完されます。
同様に、/example01/something//example01/somethingとなります。

個々の機能は非常に便利で魅力的ですが、これらが衝突した際に期待したページが表示されなかったり、リダイレクトがループするのです。

では、実際に起きた2つのケースの条件症状、そして私が行った対策を共有します。

ケース①:意図しない404

条件

  • Apache

    • RewriteRuleにより、Next.jsへプロキシ転送される

    • DirectoryIndexが有効(またはデフォルト)で、index.htmlが指定されている

    • public_html/example01/など、実ディレクトリが存在する

    • 上記のような実ディレクトリ内にindex.htmlが存在しない

.htaccess
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

症状

  1. https://example.com/example01にアクセスする

  2. ApacheのDirectorySlashにより、https://example.com/example01/に変換される

  3. ApacheのDirectoryIndexにより、/example01/index.htmlが内部的にリクエストされる

  4. そのリクエストがプロキシを通じてNext.js側へ転送される(例:http://localhost:3000/example01/index.html

  5. Next.js側には/example01/index.htmlに対応するルートが存在しないため、404エラーが返される

https://example.comへのアクセスでも同様のことが起きます

対策

  • DirectoryIndex disabledを指定して補完を抑止する

  • RewriteRuleでディレクトリアクセスを明示的に処理する

  • ルーティングと競合しうる不要な実ディレクトリは削除する

.htaccess
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 (またはデフォルト)が指定されている

症状

  1. https://example.com/example02にアクセスする

  2. ApacheのDirectorySlashにより、https://example.com/example02/に変換される

  3. そのリクエストがプロキシを通じてNext.js側へ転送される(例:http://localhost:3000/example02/

  4. Next.jsのtrailingSlashにより、スラッシュなしのURL(/example02)へリダイレクトされる

  5. https://example.com/example02にアクセスする

  6. 以下ループ

対策

  • ApacheとNext.jsでスラッシュポリシーを揃える

  • 実ディレクトリとルーティングの競合を避ける設計にする

  • ルーティングと競合しうる不要な実ディレクトリは削除する

おわりに

今回の出会いを通して、ウェブサーバーとアプリケーションの間での「ルーティングの役割分担」について、改めて理解が曖昧だったと気づかされました。

XServerとNext.jsのリバースプロキシ構成だけでなく、こういったディレクトリインデックスやトレイリングスラッシュの処理の競合は、任意のウェブサーバーと任意のアプリケーションの構成であれば起こり得ることだろうと思います。

解決までに時間はかかりましたが、とても良い経験になりました。

同じような構成でつまずいた方の助けになれば幸いです。

Discussion