Help us understand the problem. What is going on with this article?

Railsの既存コードをほぼ変更せずにAMP対応する8ステップ

More than 3 years have passed since last update.

はじめに:通信量が少ない&&爆速なAMP

AMP(Accelerated Mobile Pages)はサイト表示を高速化する技術です。特に帯域制限されたスマホでも素早く快適に画面が表示されるので、(私もそうなのですが)スマホの通信料金を節約したい!帯域細い!というユーザーには待望の技術ではないでしょうか。

しかしその高速さの一方で、AMP開発チームがその技術を

User Experience > Developer Experience > Ease of Implementation

と言い切っている通り、「UX(スピード)最優先、作りやすさは二の次!」で扱いにくい側面もあります。
参考: https://www.ampproject.org/learn/design-principles/

そんなAMPを、なるべく既存のコードを修正せず(特に、画面テンプレートにはほとんど手を入れないで)AMP対応したいと思います。(開発環境: Rails5.0.1)

スクリーンショット 2016-12-04 22.10.58.png

Googleでの検索結果の例。「光速の体験」ですって。

AMPの制約

AMP対応ページにおける制約のうち、大きなものは以下の通りです。

  1. CSSは外部ファイル使用不可、50KBまで

    • 高速なページレンダリングのため外部CSSファイルは使えず<style>タグにてhtmlページ内に実装する必要があり、50KB以上の大きなCSSも許されされません。
  2. 画像表示に<img>タグが使えない

    • AMPではロードに時間がかかるものは非同期処理されます。そのためにタグは使えず、専用の<amp-img>タグを使います。またレンダリング速度のためにwidth属性とheight属性を設定しておく必要があります。
  3. Javascriptが必須のページは対応できない

    • AMPではJavascriptの動作に制限があり、サイト開発者が用意したScriptは動作させることができません。
  4. Cookieが実質使用不可

    • Google検索結果からAMPページに遷移すると、 https://www.google.co.jp/amp/s/自サイトのドメイン/パス というGoogleドメイン下のキャッシュページが表示されます。このページは自サイトのドメインではないため、自サイトのCookie情報が使われません。つまり会員向け機能などCookieを活用した機能は提供できません。

特に3と4の制約からインタラクティブなページのAMP対応は難しく、今回は「見るだけで完結するページ」をAMP対応させることとします。

対応方針

各Railsのアクションに.amp拡張子をつけることで対応する方針とします。例えば
/domain/controller/action
というページがあった場合には
/domain/controller/action.amp
としてamp対応ページを返却することとします。

Step1. .amp を認識させる

まず、.ampをMimetypeとしてRailsに認識させます。

config/initializers/mime_types.rb
Mime::Type.register_alias "text/html", :amp

Step2. .ampで反応するようにする

AMPに対応させるactionのrespond_toメソッドに修正を加えます。

app/controllers/blogs_controller.rb
def some_action
  #   :
  # 必要な処理
  #   :

  respond_to do |format|
    format.html
    @amp_ready = true
    format.amp do
      lookup_context.formats = [:amp, :html] # .htmlのテンプレートも検索する
      render
    end
  end
end 

ここでのポイントは2つです。

rel="amphtml"のための@amp_ready変数

AMPに対応したページのオリジナルhtmlページには、「このページはAMPに対応しているよ!」と知らしめるために@amp_ready = true をセットします。詳しくは後述します。

.htmlテンプレートを使うためのlookup_context

RailsのActionViewは、拡張子に応じてテンプレートを探します。ですので.amp拡張子の場合は「.amp」がついているテンプレートしか探しません。このままでは各アクションごとに'action.amp.erb'を用意しなくてはならずツライので、.ampテンプレートがなければ.htmlテンプレートを探すように修正します。

lookup_context.formats = [:amp, :html]

これにより.htmlテンプレートの流用が可能になります(やった!)。

Step3. 従来のhtmlページにlink rel="amphtml"を付与する

さきほど@amp_ready変数を設定しましたが、この変数を利用して元のページに

<link rel="amphtml" href="https://domain/controller/action.amp"> というlinkタグを付与します。まずamp対応ページかどうかを判定するamp_ready?を実装します。

app/helpers/application_helper.rb
module ApplicationHelper
  def amp_ready?
    defined?(@amp_ready) && @amp_ready == true
  end
end

続いてapplication.htmlには

app/views/layouts/application.html.slim
(略)

html
  head
    (略)
    - if amp_ready?
      - amp_uri = URI.parse(request.url)
      - amp_uri.path = "#{amp_uri.path}.amp"
      - amp_uri.query = h(amp_uri.query) if amp_uri.query.present?
      link rel="amphtml" href = amp_uri.to_s.html_safe

のように、amp_readyの場合には<link rel="amphtml">を出力します。
細かい工夫ですが、URIオブジェクトを使うことで拡張子のあとにパラメータが付いていても正しく変換できます。
blogs/action?foo=bar => blogs/action.amp?foo=bar

Step4. AMP用レイアウトを用意する

AMPでは、<amp-*>タグ用のスクリプトなどをロードするボイラープレート(模範表現)が提供されています。これを読み込めるようにapplication.htmlを修正します。

app/views/layouts/application.html.slim
- if request.format ==  :amp
  = render 'layouts/application' #application.amp.slimを読み込む
- else
  doctype html
  html
    (以下従来のHTMLテンプレート)

読み込まれるapplication.ampは、ampproject.orgで提供されているボイラープレートをベースに改変します。
https://www.ampproject.org/docs/get_started/create/basic_markup

app/views/layouts/application.amp.slim
| <!doctype html>
html amp=""
  head
    meta[charset="utf-8"]
    meta[name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"]
    link rel='canonical' href = request.url.gsub('.amp', '')
    style[amp-boilerplate]
      | body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}
    noscript
      style[amp-boilerplate]
        | body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}
    style[amp-custom]
      - controller = params[:controller]
      - if Rails.application.assets && Rails.application.assets["amp/amp_#{controller}"]
        // development
        = Rails.application.assets["amp/amp_#{controller}"].to_s.html_safe
      - else
        // production
        = File.read "#{Rails.root}/public#{stylesheet_path("amp/amp_#{controller}", host: nil)}"
    script[async src="https://cdn.ampproject.org/v0.js"]
    = render 'header'
    = yield
    = render "footer"

Step5. CSSの対応

前述のapplication.ampで、styleタグの部分にRails.application.assetsの処理が入っていたのに気づきましたか?
「AMPの制約」に記載したとおり、CSSは外部ファイルを利用できず、また容量制限があります。そこで以下のような方針で対応します。

  • asset pipelineで作られるCSSをstyleタグで読み込む
  • コントローラごとにAMP用CSSを作成することで容量を削減する

先のコードをもう一度確認します。

style[amp-custom]
  - controller = params[:controller]
  - if Rails.application.assets && Rails.application.assets["amp/amp_#{controller}"]
    // development
    = Rails.application.assets["amp/amp_#{controller}"].to_s.html_safe
  - else
    // production
    = File.read "#{Rails.root}/public#{stylesheet_path("amp/amp_#{controller}", host: nil)}"

Rails.application.assetsに注目してください。
開発環境ではRails.application.assetsにコンパイルされたCSSが格納されています。残念ながら本番環境ではこの変数はnilになっていますが、代わりにasset precompileしたファイルをstylesheet_pathヘルパによって取得することが可能です。そのファイルをFile.readで読み込みます。

読み込まれるAMP用CSS自体はconfig/applicatio.rbにてasset precompile対象に設定しておきます。AMP対応するファイルが増えるたびapplication.rbをメンテするのは面倒なので、app/assets/stylesheets/amp以下のCSSファイルはすべてasset precompile対象にします。

config/application.rb
 amp_css_paths = Dir.entries("#{config.root}/app/assets/stylesheets/amp").select { |name| name =~ /css$/ }.map { |name| "amp/#{name}" }
 config.assets.precompile += amp_css_paths

参考: https://coderwall.com/p/wpyasq/how-to-create-an-amp-page-for-your-dynamic-content-in-rails

読み込むCSSファイルは"amp/amp_#{controller_name}"とし、オリジナルのapplication css の中から必要なものを取捨選択します。(50KBを超えなければよいので、小規模サイトならオリジナルのコンパイル済みCSSをそのまま使えるかもしれません。)

app/assets/stylesheets/amp/amp_blogs.scss
// application.scssをコピーしてきたもの
// @import "news"
@import "blogs" //これだけ使う
// @import "comments"

Step6. image_tagヘルパーのフック

「AMPの制約」に記載したとおり、すべての<img>タグは<amp-img>タグに置き換えないといけません。そこでimage_tagをフックし、必要に応じて<amp-img>が出力されるようにします。

config/initializers/image_tag_helper.rb
module ActionView
  module Helpers
    module AssetTagHelper
      def amp_image_tag(source, options = {})
        src = options[:src] = ActionController::Base.helpers.path_to_image(source, skip_pipeline: options.delete(:skip_pipeline))

        unless src.start_with?('cid:', 'data:') || src.blank?
          options[:alt] = options.fetch(:alt) { ActionController::Base.helpers.image_alt(src) }
        end
        options[:layout] ||= 'fixed'
        ActionController::Base.helpers.tag('amp-img', options)
      end

      # amp-imgかimgかをrequest.formatによって使い分ける。
      def image_tag_with_amp(source, options = {})
        if amp?
          amp_image_tag(source, options)
        else
          image_tag_without_amp(source, options)
        end
      end

      def amp?
        Thread.current[:format] == 'amp'
      end
      alias_method_chain :image_tag, :amp
    end
  end
end

参考: https://github.com/rails/rails/blob/5-0-1/actionview/lib/action_view/helpers/asset_tag_helper.rb#L210

ポイントは2点です。

  • ampの場合はamp_image_tagメソッドをコールし、ampでない場合は従来のimage_tagメソッドをコールする。(image_tag_with_amp)
  • このヘルパーはrequestオブジェクトにアクセスできないので、Thread.current(グローバルオブジェクト)を用いてampかどうかの情報を引き渡します(amp?)。その情報は以下のようにapplication_cotrollerにてセットします。
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :set_format

  def set_format
    Thread.current[:format] = request[:format]
  end
end

以上により、ampのときだけが使われるimage_tagメソッドができました。

この変更を加えた上でimage_tag使用箇所全てにwidht, height属性を設定します。

Step7. JSON-LDへの対応(optional)

AMPではJSON-LDと呼ばれる構造化データを利用します。構造化データはタイトルや画像、作成日などのページのメタ情報をmachine-readableにするものです。サイトの性質によって異なりますので一概には言えませんが、ブログ、コラム、ニュースの類であればArticleというフォーマットを利用します。

参考: https://developers.google.com/search/docs/data-types/articles#amp-sd
参考: http://schema.org/NewsArticle

Step8. デバッグ

AMPのバリデータはchromeに標準搭載されています。
URLに#development=1をつけてページを開き、
Chrome DevTool Consoleを立ち上げると、バリデーション結果が表示されます。
image
こちらで表示されたエラーを確認しながら、すべて対応していきます。
参考: https://www.ampproject.org/docs/guides/validate

最後に

おつかれさまでした!対応完了までにはちょっと手数が多いですが、画面テンプレートにはほとんど手を加えずに対応できたかと思います。
また、上記対応では触れませんでしたが、

  • フォームがあるページには<amp-form>
  • GoogleAnalyticsを仕込んでいるページには<amp-analytics>
  • Adsenseなど広告を入れているページには<amp-ad>

を使って対応する必要があります。ビジネスユースではこれに加えて効果測定上の工夫も必要となるかと思いますが、個人ユースでは本稿の内容で概ね対応できると思います。

Make our mobile web faster, together!

zucay
livesense
求人情報サイト「マッハバイト」「転職ナビ」「転職会議」「就活会議」「転職ドラフト」および不動産情報サイト「IESHIL」などを開発・運営しています。
https://www.livesense.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした