しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET MVC の Display Mode を使っている場合には Vary HTTP ヘッダーを出力するべきだった

ASP.NET MVC の Display Mode を使えば、ビューを用意するだけで PC 版とスマートフォン版のページを同じ URL で公開することができます。

既に何回か紹介しているので、Display Mode については以下の記事を参考にしてください。

機能としては上の記事の通りにすれば問題ないですが、Google のドキュメントに動的な配信を行う場合には Vary HTTP ヘッダーを使って、User-Agent で異なるビューが返されることを伝えるべきとありました。

動的な配信の場合、ページをリクエストするユーザー エージェントに応じて、同じ URL で異なる HTML(および CSS)が配信されます。

この設定では、PC 用ユーザー エージェントのクロール時にはモバイル コンテンツが隠されているため、モバイル用ユーザー エージェント向けにサイトの HTML が変更されることはすぐにはわからない状態になっています。サーバーからヒント送信し、スマートフォン用 Googlebot がページをクロールしてモバイル コンテンツを検出するようリクエストすることをおすすめします。このヒントは Vary HTTP ヘッダーを使用して実装します。

モバイルファースト インデックスに関するおすすめの方法 | Google 検索セントラル  |  ドキュメント  |  Google for Developers

適切にヒントをクローラーに与えていない場合には、検出されるまでに時間がかかることがありそうです。

Vary HTTP ヘッダーを出力する

結局のところ Vary HTTP ヘッダーで User-Agent を返せば良いだけなんですが、IIS の圧縮モジュールを使っている場合には簡単にはいきません。モジュールに既知の不具合があるからです。

IIS で gzip 圧縮が有効になっている場合、強制的に Vary HTTP ヘッダーの値が Accept-Encoding に上書きされてしまいます。残念ながら直る気配がありません。

この時は gzip 圧縮をオフにして対応しましたが、それだと効率が悪化するので URL Rewrite を使いました。

<system.webServer>
  <rewrite>
    <outboundRules>
      <rule name="Append Vary" preCondition="IsHTML">
        <match serverVariable="RESPONSE_Vary" pattern="^.*$" />
        <action type="Rewrite" value="{R:0}, User-Agent" />
      </rule>
      <preConditions>
        <preCondition name="IsHTML">
          <add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/html" />
        </preCondition>
      </preConditions>
    </outboundRules>
  </rewrite>
</system.webServer>

URL Rewrite Module はサーバー変数を書き換えると、レスポンスヘッダーにも反映されます。

この定義を Web.config に張り付けるだけで、HTML を返した時だけ Vary HTTP ヘッダーに User-Agent が追加されます。実際にブラウザからアクセスして確認してみました。

Content-Encoding は gzip のままで、Vary には Accept-Encoding と User-Agent が追加されています。

URL Rewrite の Outbound Rule 処理は HTTP 圧縮が行われた後に実行されるので、影響を受けずに HTTP ヘッダーを操作することが出来る、というからくりでした。

おまけ:Vary HTTP ヘッダーが存在しない場合

ついでに圧縮を無効にした場合の挙動も一緒に確認しておきました。

HTTP 圧縮モジュールが Vary を出力しないため、User-Agent のみ出力されています。理想的な挙動です。