mod_rewriteは指定順が大事。WordPressなどのルーティング設定に注意

WordPressやその他のCMS、フレームワークを採用している時に.htaccessに記述したmod_rewriteが効いてくれなくて、URLの書き換えができない…そんな時、フレームワークに採用されている「ルーティング機能」のためのルールセットと、独自のルールセットの順番を入れ替えてみるとサクッと解決することがあります。

ルーティング用の設定とは?

WordPressだけでなく多くのフレームワークには、リクエストされたURLに対象となる実体(Webページや画像など)が存在し⁠ない場合⁠に、システム本体に要求されたURLであると認識し、リクエストされたURLの解析を行って適切な処理を実行する機能があります。

  1. リクエストされたURLに対象となる実体(Webページや画像など)が存在し⁠ない場合⁠に、
  2. リクエストされたURLの解析を行って適切な処理を実行する

この後半「2」の機能が「URLルーティング」で、WordPressでは/index.phpが「URLルーター」としてリクエストされたURLを解析し、適切なコントローラーを呼び出すことで処理が実行されページが表示される、という仕組みになっています。

で、前半「1」の「リクエストされたURLに対象となる実体が存在し⁠ない場合⁠に」そのルーターにリクエストを集める役割を担っているのが.htaccessに書かれたルーティング用の設定=mod_rewriteの次のルールセットです。

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

5行目は/index.phpが直接呼ばれた場合の処理で、何もせずリライトを停止させます。
6行目7行目が「該当するファイルやディレクトリの実体がない場合は」という書き換えの条件で、該当する場合その下8行目「全て./index.phpに書き換える」という処理が実行されます。

つまり、実体のないURLへのリクエストは、すべてルートにある/index.phpに渡すと指定しているのです。(これが「WordPressはデフォルトだと404ページが表示されない」理由です)

基本的なmod_rewriteの処理の流れ

ここでmod_rewriteの処理の流れに関するルールをおさらいします。

  • mod_rewriteは上から順番に処理が実行される
  • RewriteRuleのフラグに[R][L]が付与された場合、条件にマッチした時点で書き換え処理を停止し、それ以降の書き換えは行わない。(以降のルールセットは評価されない)

よって、指定する順番が挙動に影響します

ルールセットの指定順に注意

ルーティング用のルールセットRewriteRuleのフラグに[L]が指定されているので、条件にマッチして書き換え処理が実行された場合、それより後のルールセットはスルーされます。 「実体がないURL」の「すべてを」という条件はかなり対象が広いので、これが一連のリライト処理の初めの方に指定されると、本来処理してほしいルールセットに行きつくまでに処理が停止されて、意図した結果が得られない、ということが起こります。

例1)動的URLを静的化するためのルールセット

パラメータ付きの動的なURLを静的化するため、次のようなルールセットを用意したとします。

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^products/detail-([0-9a-zA-Z_-]+).html?$ products/detail.php?product_id=$1 [L,QSA]

この場合「ファイルやディレクトリの実体がないURLの場合は」という書き換えの条件はルーティングの設定と同じで、違いは、対象が「リクエストされたパスがproducts/detail-**で始まる」場合に限定されているところです。ルーティングの設定は「全て.」が対象なので、より詳細度の高いこのルールセットが上位にないとルールは実行されません

例2)強制的にステータスコード404を返すためのルールセット

何らかの理由で、特定のURLに対し強制的にステータスコード404を返すため、次のようなルールセットを用意したとします。

RewriteCond %{REQUEST_URI} ^/payment.html$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/buy.html$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/searches/.*$ [NC]
RewriteRule ^.*$ - [R=404,L]

この場合も同様で、強制的に404を返したいURLは実体がないはずです。よってこれよりルーティング用の設定が先にある場合、そちらで書き換えが処理されてしまうので、このルールセットが実行されることはずっとないのです。

ルーティング用の設定は一番最後に

「実体がないURL」の「すべて」より緩い条件でURLの書き換えが必要になるケースはありません。なのでルーティング用の設定は、mod_rewriteブロックの一番最後に記述するようにしましょう!

既にWordPressが使用されているサイトに手を加える場合など、ついついルーティングの設定より後に追記する形でmod_rewriteのルールセットを追加して「動かない!」ってなっているのを見かけたり、「URLの正規化や静的化がうまくいってないからちょっと見て欲しい」と言われて覗くと、同様になっているケースがよくあるので、「おや?」と思ったら確認してみてください。