Webサイトリニューアル時に個別にURLをリダイレクトする方法(Apache RewriteMap の設定)


2017年に自社Webサイトをリニューアルして、旧URLを2つのWebサイトに一つ一つ対応づけてリダイレクトした時の方法(Apache の mod_rewrite の設定サンプル)を書いておきます。

※ この記事は、基本的なリライト設定は書けるよ、という方を対象に書いています。

この方法の概要

  • Apache の mod_rewrite を使う。
  • RewriteMap 機能を使う。サーバ設定ファイル(= .htaccess ではなく httpd.conf)を編集できる権限が必要。
  • URLの対応自体は、人の目で見て考えなければならない。
  • 一度設定しておけばサーバ管理者ではなくてコンテンツ担当者がリダイレクトの設定を更新できる。
  • 設定ファイルの形式はTSVファイル(タブ区切りテキスト)。エクセルで編集可能。
  • 全部で500URL程度のリダイレクトに使った方法。

Webサイトリニューアルの時にリダイレクトを設定できない(しない)理由の一つは、おそらく、サーバ管理者チームとコンテンツ管理チーム(または社外制作者と社内担当者)が打ち合わせして業務分担する段階ではまだ個々のURLの対応が決まっていないからでは無いかと思う。

リニューアルの際は多かれ少なかれ中身の書き換えも行い、ページが分割・統合される。新しいWebサイトのページの内容を書いてみてから新旧の対応を変えたくなる事も多い。

という事で、コンテンツ編集担当者がページの更新と同時にリダイレクトの設定も更新できる設定方法の紹介です。

.htaccess には書けない RewriteMap 機能を使うので、初回設定時にはサーバ設定ファイル(httpd.conf)を更新してWebサーバの再起動が必要。

RewriteMap を指定した Apache の設定サンプル

※このままコピーしても動きません!

RewriteMap map-name1 txt:/path/to/user/dir/map1.tsv
RewriteCond ${map-name1:$1|nopage} !nopage
RewriteRule ^(/support/[0-9]+)/?(index\.html)? "http://support.example.com${map-name1:$1}" [R=301]

RewriteMap map-name2 txt:/path/to/user/dir/map2.tsv
RewriteCond ${map-name2:$1|nopage} !nopage
RewriteRule ^/sample/([0-9]+-[^/]*)(/.*) "http://www.example.com/ja/samples/${map-name2:$1}$2" [R=301]

RewriteMap map-name3 txt:/path/to/user/dir/map3.tsv
RewriteCond ${map-name3:$1|nopage} !nopage
RewriteRule ^(/.*\.html) "${map-name3:$1}" [R=301]

RewriteMap 設定解説

RewriteRule と RewriteCond の関係

RewriteRule と RewriteCond の関係は少しトリッキーなのでまずそこから解説します。

一番シンプルな、上記例で最後の設定を例に取ります。

RewriteMap map-name3 txt:/path/to/user/dir/map3.tsv
RewriteCond ${map-name3:$1|nopage} !nopage
RewriteRule ^(/.*\.html) "${map-name3:$1}" [R=301]

RewriteCond は RewriteRule の設定を適用する条件を指定できます。そして、その条件は、当該 RewriteCond の直後に現れる RewriteRule に適用されます。

つまり、上記の例だったら、

RewriteCond ${map-name3:$1|nopage} !nopage

RewriteRule ^(/.*\.html) "${map-name3:$1}" [R=301]

を処理する条件だけを規定する、ということです。

ただその際、RewriteCond で条件を判別する対象は、そもそも後に書かれている RewriteRule に引っかかってくるURLだけだ、という約束があります。

つまり、以下の順番で処理されているわけです。

  1. ^(/.*\.html) にマッチする?
  2. ${map-name3:$1|nopage} !nopage を満たす? (この文法については後述)
  3. じゃあ書き換え! ^(/.*\.html) → “${map-name3:$1}” [R=301]

RewriteMap の設定時に RewriteCond を噛ませる理由

次に、なんでこんな条件を噛ませるのか、という話です。

RewriteMap 機能は、Apache サーバ設定ファイルとは別のファイルにURL書き換えの設定を書いておけるよ、という機能です。RewriteCond を設定せずにこの機能を使ったとすると、例えば、設定は以下のようになります。

RewriteMap map-name3 txt:/path/to/user/dir/map3.tsv
RewriteRule ^(/.*\.html) "${map-name3:$1}" [R=301]

処理の順番

  1. ^(/.*\.html) にマッチする?
  2. じゃあ書き換え! ^(/.*\.html) → “${map-name3:$1}” [R=301]

この時、^(/.*\.html) にマッチする全てのURLのリダイレクト設定が map3.tsv に書いてあれば問題ありませんが、実際には以下のような理由で全部網羅されていないことがあります。

  • 単純に抜けていた
  • まだリダイレクト先が決まっていない
  • まだリダイレクト先のページができていない
  • このページは削除したいのでリダイレクト先は無い(ページが無くなった事を明確に閲覧者や検索エンジンに伝えたい)

^(/.*\.html) にマッチしたのに map3.tsv の中にリダイレクト先の設定が含まれていない場合、エラーになったり、意図しないページに転送されたりします(具体的に何が起こるかは RewriteRule の書き方に依ります)。このため、一旦 RewriteCond を噛ませて map3.tsv の中に設定があったものだけを選んで RewriteRule の処理に渡すのが、以下の行です。

RewriteCond ${map-name3:$1|nopage} !nopage

${map-name3:$1|nopage} は RewriteMap を使う時の文法で、以下のように指定します。

${ マップの名前 : 設定ファイルの中を探すキー | デフォルト値 }
マップの名前 RewriteMap の一つ目の引数で指定したもの。
設定ファイルの中を探すキー リダイレクト元のURLを示す文字列。RewriteMapファイル(ここで紹介しているのは TSV形式)の1列目に指定しておく。
デフォルト値 指定した行が見つからなかった時に返す値。

こういうのって何て呼ぶんだろう? 動的変数? プチ関数? まあとにかく、このように指定すると、RewriteMapファイルの中の1列目に指定したキーがある行を探して、あればその行の2列目の値を戻してくれる。もしマッチする行が見つからなければ、「デフォルト値」で指定した文字列を戻してくれる、という仕組み。

上記の例で「設定ファイルの中を探すキー」に指定している「$1」は、RewriteRule 行の正規表現中のカッコにマッチした文字列。

この場合は、^(/.*\.html) と指定しているので、例えば、 /company/history.html などになる。

map3.tsv ファイルの中に

/company/history.html    http://some.example.co.jp/who-we-are/

などの指定があれば「http://some.example.co.jp/who-we-are/」が戻されるし、もしそのような行がなければ「nopage」が戻される。

ということで、以下の行は、戻されて来た値が設定が無かった事を示す「nopage」ではない事、つまり、RewriteMapファイル内に設定があった事を確認しているのです。

RewriteCond ${map-name3:$1|nopage} !nopage

RewriteMap 設定を複数連結して担当者やファイルを分ける

基本的な設定方法は以上の通りです。全てのリダイレクト対象URLを一つのRewriteMapファイルにまとめて指定もできますが、そうすると大規模サイトではファイルが大きくなり管理が大変。そこで、RewriteMap 設定を連結してファイルを分けているのが、最初の設定サンプルです。

RewriteRule で振り分けできる条件ごとにRewriteMapファイルを分けて、ファイルの管理の手間を軽減しています。いくつかのパターンを紹介します。

RewriteMap map-name1 txt:/path/to/user/dir/map1.tsv
RewriteCond ${map-name1:$1|nopage} !nopage
RewriteRule ^(/support/[0-9]+)/?(index\.html)? "http://support.example.com${map-name1:$1}" [R=301]

この設定は以下のようなリダイレクトを意図。旧Webサイトでは「/support/(番号)」だったURLを、新サイトでは、カテゴリごとにディレクトリに分けて新たな番号で登録し直したもの。

http://www.example.com/support/123/
http://support.example.com/category/subcategory/99.html

太字の部分は固定なので、設定ファイルに書かなくても良いのです。むしろ、ファイルに書かないでおくと、テスト環境と本番環境で設定ファイルを共有できるので便利です。

RewriteMapファイル map1.tsv の記載内容は以下のようになります。
※タブは、ブログ上ではスペースに変換して表示。

/support/12       /category2/subcategory2/3.html
/support/123      /category/subcategory/99.html
/support/456      /category2/subcategory2/94.html

※ 私のリニューアル事例では、番号の対応づけに規則がありませんでした。この対応づけの決定はリダイレクトの設定とは全く別の話で手間がかかりました。この類のことが、リニューアル時のリダイレクトを設定する中でおそらくもっとも時間を必要とする作業になると思います。

なお、RewriteRule の条件が長いのは、以下のURLをもれなくリダイレクトするためです。

  • http://www.example.com/support/123
  • http://www.example.com/support/123/
  • http://www.example.com/support/123/index.html

次のルール

RewriteRule は上から順番に評価されていくので、最初の RewriteRule に該当しなかったURLと、該当しても map1.tsv に記載の無かったURLは、続いて記載されているルールに当てはまるかを評価されていきます。

RewriteMap map-name2 txt:/path/to/user/dir/map2.tsv
RewriteCond ${map-name2:$1|nopage} !nopage
RewriteRule ^/sample/([0-9]+-[^/]*)(/.*) "http://www.example.com/ja/samples/${map-name2:$1}$2" [R=301]

このルールで意図したのは、以下のようなリダイレクト。旧サイトの /sample/ 配下の子ディレクトリを、新サイトの /ja/samples/ 配下に新しい名前で移動させたい、という設定です。

http://www.example.com/sample/10-blog/admin/admin.cgi
→ http://www.example.com/ja/samples/responsive-blog/admin/admin.cgi

http://www.example.com/sample/10-blog/webdir/css/common.css
→ http://www.example.com/ja/samples/responsive-blog/webdir/css/common.css

子ディレクト内のファイル階層(太字)は変わりません。

RewriteMapファイル map2.tsv の記載は以下の通り。新旧Webサイトの親ディレクトリは決まっているので、ディレクトリ名の対応だけを記載しています。
※タブは、ブログ上ではスペースに変換して表示。

10-blog          responsive-blog
15-news-updater  latest-news
注意(老婆心)

RewriteRule のURL指定部分(第2引数)に指定してある「$2」は、RewriteMapファイル内の2列目ではなく、RewriteRule の正規表現で指定した2番目のカッコ内にマッチした文字列です。混同しないようにご注意を。RewriteMapファイル内の記載は、どこをどう捻っても、2列目に記載したものしか出て来ません。

連結最後の規則

RewriteMapファイルを利用した RewriteRule のセットは幾つでも書くことができますが、最後に、規則性がなく個別にリダイレクトさせるURLの設定をしておきます。

RewriteMap map-name3 txt:/path/to/user/dir/map3.tsv
RewriteCond ${map-name3:$1|nopage} !nopage
RewriteRule ^(/.*\.html) "${map-name3:$1}" [R=301]

RewriteMapファイル map3.tsv の中身はこんな感じです。まとめられるほどの規則性が無いので、URL全体を判別して、新サイトにリダイレクトします。
※タブは、ブログ上ではスペースに変換して表示。

/about/about.html     http://www.example.com/about/
/about/legal.html     http://support.example.com/general-information/terms/legal.html
/contact/form.html    https://www.example.com/contact/

RewriteMapファイル管理について

ファイルの編集方法

RewriteMapファイルにはTSV以外の形式も使えますが、紹介したファイル形式、TSV(タブ区切りテキスト)はテキストエディタの他 Microsoft Excel でも編集できます。

コメントの記載

加えて、Apache の RewriteMap は、3列目以降はコメントとして丸無視してくれるので、3列目以降にURLの内容や管理者用コメントを書いて置くことができます。

編集と責任範囲

RewriteMapファイルはサーバ設定ディレクトリに置かなくてもよく、Webサーバユーザで読み取れればホスト内の何処に置いてもいいので、技術者で無くてもファイル編集→アップロードでリダイレクトの設定を変えられるので便利です。更新時のWebサーバ再起動も不要です。

RewriteCond の条件が同じでも、担当者や担当部署によってRewriteMapファイルを分けておけば、編集ミス等による不要なエラーを避けると共に、責任範囲の切り分けにも役に立ちます。