🐬

【動的OGP】 Rails7, Dockerを使ってXシェアを実装する

2024/09/09に公開

はじめに

こんにちは!! プログラミング初学者のyukimuraです!
今回は私自身の個人開発したアプリで実装したXシェア機能について記述しています
間違いなどありましたら、優しくご指摘いただけましたら幸いです。

個人開発アプリ
https://scammers-notebook.com/
GithubURL
https://github.com/yuki51b/scammer-s-notebook

前提

今回アプリのデプロイはRender.comを使用しました
Render.comでの画像保存はAmazonS3の使用が必要ですが、Xシェアのみの画像使用であれば保存まで必要なかったので、今回は画像保存をせずに都度画像を生成するアプローチでの動的OGPを実装します
今回の実装では、下記記事を大変参考にさせていただきました
https://qiita.com/codeq_official/items/848c705252075c631e29
https://zenn.dev/goldsaya/articles/ba945b877daa07

環境

  • docker
  • rails 7.0.8.4
  • ruby 3.2.3
  • tailwind

OGPとは

OGPとは「Open Graph Protocol」の略で、Webサイト内の記事がFacebookやTwitterなどのSNSで拡散・シェアされた際に、各ページのタイトルやサムネイル画像といった情報を表示させるための機能です。

OGP(Open Graph Protocol)を設定する目的は、URLがソーシャルメディアやメッセージングアプリでシェアされたときに、そのURLに関する情報を豊かに表示することになります。
簡単にいうと、URLを共有したときにただURLが共有されるのではなくて、画像などのその他の情報が装飾されて表示されるということです

  • OGPを設定していないとき
    Image from Gyazo
  • OPGを設定したとき(これは静的です)
    Image from Gyazo
  • 動的OGPの時(今回の実装です)ユーザーが作成したタイトルが表示されます
    Image from Gyazo

事前準備

背景画像を用意してください

Canvaなどのアプリで背景画像を作成してください。
https://www.canva.com/
自分のアプリでは下記の画像を用意しました
大きさは1200 x 627です
Image from Gyazo

画像の大きさは参考記事の下記部分を参考にしました
https://zenn.dev/goldsaya/articles/ba945b877daa07#5.-top_imageの作成

使用するフォントを決めてダウンロードをしてください

無料でのフォントは多くあります。ご自身の好みのフォントをご用意ください

使用するgemとその役割

今回のメインとなるgemは2つです

1. gem meta-tags

https://github.com/kpumuk/meta-tags
metaタグを設定して、URLがシェアされた時に画像などの情報で装飾をする役割の為に使用します

2. gem mini_magick

https://github.com/minimagick/minimagick
metaタグでURLがシェアされた時にユーザーが作成したタイトルと用意した画像を合成する役割のために使用します

実装を開始する

画像の合成を行える様にする

1. gem mini_magickを使用するためにImageMagickを導入する

mini_magickを使用するには、ImageMagickが必要です。
簡単にいうと、ImageMagickを各言語で使いやすくしたものの一つがMiniMagickだからです
https://imagemagick.org/#google_vignette
https://imagemagick.org/script/develop.php

DockerFileに記述する

DockerFileにimagemagickを記述して導入する

# syntax = docker/dockerfile:1

ARG RUBY_VERSION=3.2.3
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base

WORKDIR /rails

ENV RAILS_ENV="production" \
    BUNDLE_DEPLOYMENT="1" \
    BUNDLE_PATH="/usr/local/bundle" \
    BUNDLE_WITHOUT="development"

FROM base as build

RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential curl git libpq-dev libvips node-gyp pkg-config python-is-python3


ARG NODE_VERSION=20.13.1
ARG YARN_VERSION=1.22.22
ENV PATH=/usr/local/node/bin:$PATH
RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \
    /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \
    npm install -g yarn@$YARN_VERSION && \
    rm -rf /tmp/node-build-master

COPY Gemfile Gemfile.lock ./
RUN bundle install && \
    rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
    bundle exec bootsnap precompile --gemfile

COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile

COPY . .

RUN bundle exec bootsnap precompile app/ lib/
ENV SECRET_KEY_BASE=Rails.application.config.secret_key_base=ENV['SECRET_KEY_BASE']
RUN ./bin/rails assets:precompile

FROM base


### この部分のpostgresql-clientの後ろにimagemagickを追加 ###
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y curl libvips postgresql-client imagemagick && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives



COPY --from=build /usr/local/bundle /usr/local/bundle
COPY --from=build /rails /rails

RUN useradd rails --create-home --shell /bin/bash && \
    chown -R rails:rails db log storage tmp
USER rails:rails

ENTRYPOINT ["/rails/bin/docker-entrypoint"]
EXPOSE 3000
CMD ["./bin/rails", "server", "-b", "0.0.0.0", "-p", "3000"]

その後

  • docker compose build等を行ってください
  • また、コンテナ内で下記コマンドを使ってImageMagickが導入されているか確認してください
convert -version

Image from Gyazo

2. gem 'mini_magick'の導入

Gemfileに記述

gem 'mini_magick'

その後

bundle install

3. 画像生成機能を実装する

  • ダウンロードしたフォントファイルを、app/assets/fonts配下に配置してください
  • app/controllers/concerns以下にogp_creator.rbを作成してください
    これは、画像を作成するクラスです
    下記のコードを記述して、背景画像ファイルのパス・フォントファイル名をそれぞれ記載してください
class OgpCreator
  require 'mini_magick'  
  BASE_IMAGE_PATH = './app/assets/images/{ 背景画像ファイル名を記載 }'
  GRAVITY = 'center'
  TEXT_POSITION = '0,0'
  FONT = './app/assets/fonts/{ フォントファイル名を記載 }'
  FONT_SIZE = 65
  INDENTION_COUNT = 16
  ROW_LIMIT = 8

  def self.build(text)
    text = prepare_text(text)
    image = MiniMagick::Image.open(BASE_IMAGE_PATH)
    image.combine_options do |config|        
      config.font FONT
      config.fill 'red'
      config.gravity GRAVITY
      config.pointsize FONT_SIZE
      config.draw "text #{TEXT_POSITION} '#{text}'"
    end
  end

  private
  def self.prepare_text(text)
    text.to_s.scan(/.{1,#{INDENTION_COUNT}}/)[0...ROW_LIMIT].join("\n")
  end
end

4. OGP用のコントローラーを作成

  • viewファイルは必要ないので作成しない
bin/rails g controller images ogp --skip-template-engine
  • 下記コードを記述する(アプリではsorceryの認証を実装しているので、skip_before_actionを設定しています)
class ImagesController < ApplicationController
  skip_before_action :require_login, raise: false

  def ogp
    text = ogp_params[:text]
    image = OgpCreator.build(text).tempfile.open.read
    send_data image, type: 'image/png', disposition: 'inline'
  end

  private

  def ogp_params
    params.permit(:text)
  end
end

解説は参考記事のこちらを見てください
https://qiita.com/codeq_official/items/848c705252075c631e29#8-imagescontrollerファイルを修正

5. ルーティングの設定

config.routus.rbにルーティングを設定

  get 'images/ogp.png', to: 'images#ogp', as: 'images_ogp'

6. 動作を確認する

URLで/images/ogp.pngにアクセスし、textパラメーターを追加して見てください
下記画像はURLがlocalhost:3000/images/ogp.png?text=おはようの場合の画像です
Image from Gyazo

  • textパラメーターをhogehogeなど文字に変更して画像が変わったら成功です

Metaタグを実装する

次に、gem 'meta-tags'を導入

1. Gemfileに記述

gem 'meta-tags', require: 'meta_tags'

その後

bundle install

2. meta_tags:installコマンドで設定ファイル追加

コンテナに入って、下記コマンドでconfig/initializers/meta_tags.rbを作成します

rails generate meta_tags:install

作成されたファイルは特に記述を追加せずに大丈夫です

config/initializers/meta_tags.rb
# frozen_string_literal: true

MetaTags.configure do |config|

end

3. application.html.erbにdisplay_meta_tagsメソッドを追加する

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= page_title(yield(:title)) %></title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %>
    

    <!-- meta_tag -->
    <%= display_meta_tags %>
  </head>

4. postコントローラーにmetaタグの設定を記述する

今回は普通の投稿機能(postコントローラー)を使って実装しました
Postテーブルにはtitleカラム、bodyカラムなどがあります
ユーザーがcreateアクションによって、作成・保存したtitleカラムの値を表示する用にします

postコントローラーを下記の様に記述します(該当部分以外省略しています)

posts_controller.rb
class PostsController < ApplicationController
## 設定したprepare_meta_tagsをprivateにあってもpostコントローラー以外にも使えるようにする
  helper_method :prepare_meta_tags

  def show
    @post = Post.find(params[:id])
    ## メタタグを設定する。
    prepare_meta_tags(@post)
  end

  private

  def prepare_meta_tags(post)
## このimage_urlにMiniMagickで設定したOGPの生成した合成画像を代入する
    image_url = "#{request.base_url}/images/ogp.png?text=#{CGI.escape(post.title)}"
    set_meta_tags og: {
                    site_name: '詐欺師の手帳',
                    title: post.title,
                    description: 'ユーザーによる詐欺被害の投稿です',
                    type: 'website',
                    url: request.original_url,
                    image: image_url,
                    locale: 'ja-JP'
                  },
                  twitter: {
                    card: 'summary_large_image',
                    site: '@https://x.com/yukimura877',
                    image: image_url
                  }
  end
end

解説

  • helperファイルでの設定はアプリ全体でのデフォルトの設定を行えるものなので、
    今回はshow画面の内容だけがシェアできたらいいので設定をコントローラーで行う。
    set_meta_tagsはgem meta-tagsが用意しているメソッドで、ページなどに合わせてメタタグを設定することができる

  • image_url = "#{request.base_url}/images/ogp.png?text=#{CGI.escape(post.title)}"ここで画像を合成しています
    (post.title)はshowアクションで取得したユーザーが作成したpostテーブルのtitleカラムにある値を代入しています

自分のアプリでは上記の様に設定しました
set_meta_tags og:以降の設定は参考記事の下記部分を参照してください
https://zenn.dev/goldsaya/articles/ba945b877daa07#4.-default_meta_tagsメソッドの定義%2Fogpの設定

5. show.html.erbで設定する

  • show.html.erbファイルにXシェアボタンを導入し、meta-tagを設定します
views/posts/show.html.erb
<div class="text-gray-600">
    <% prepare_meta_tags @post %> ## 設定したメタタグをリダイレクトされる前に呼び出す
    <% twitter_share_url = "https://twitter.com/share?url= #{CGI.escape(post_url(@post))}" %> ## post_urlにしないと完全なURLにならない
    <%= link_to twitter_share_url, target: '_blank', data: { toggle: "tooltip", placement: "bottom" }, title: "Xでシェア" do %>
## Xシェアボタンはfontawesome.comから使用しています
    <i class="fa-brands fa-square-x-twitter fa-2xl"></i>
    <% end %>
</div>

補足

  • <% twitter_share_url = "https://twitter.com/share?url= #{CGI.escape(post_url(@post))}" %> ここでは、シェアされるURLを設定します。
    今回は、他のユーザーがOGPにアクセスしたときにposts/show/該当のIDのshowページにアクセスして欲しいので、post_url(@post)と設定します
  • CGI.escapeを使用して、URLの文字をエンコードしています
    https://docs.ruby-lang.org/ja/latest/method/CGI/s/escape.html

実装を確認する

本番環境にpushしなくても、下記Chrome拡張機能を使うとローカルでも確認できます

1. cromeに下記の拡張機能を追加してください

https://chromewebstore.google.com/detail/localhost-open-graph-debu/kckjjmiilgndeaohcljonedmledlnkij

2. 追加を完了したら、下記動画の様にサーバーを立ち上げてください。

ローカルホストでのURLを変更して新しいURLを作成してくれます
Image from Gyazo

3. 上記で生成されたURLをコピーした後に、Xのリンクボタンをクリックしてください

4. 先ほどコピーしたURlを貼り付けて、Preview.cardをクリックしてください

Image from Gyazo

ここでのlogで、successとなっていたら設定はできています!!

5. コピーしたURLを実際のツイッター投稿で貼り付けても確認できます

Image from Gyazo

  • また、デベロッパーツールでもmeta-tagが設定されているかの確認もできます
    Image from Gyazo

おまけ(静的OGPの設定)

静的OGPの実装も記述します

1. 静的OGPのみならばgem MiniMagickの導入部分とフォントのダウンロードは必要ありません

最初に下準備と同じように、装飾した画像を作成してください
自分のアプリでは、下記画像にしました
大きさも動的OGPと同じです
Image from Gyazo

2. application_helper.rbにデフォルトのOGPを設定します

application_helper.rb
# frozen_string_literal: true

module ApplicationHelper
  def default_meta_tags
    {
      site: '詐欺師の手帳',
      title: '詐欺師の手帳',
      reverse: true,
      charset: 'utf-8',
      description: '詐欺被害の未然防止を目的としたアプリです',
      canonical: request.original_url,
      og: {
        site_name: '詐欺師の手帳',
        title: '詐欺師の手帳',
        description: '詐欺被害の未然防止を目的としたアプリです',
        type: 'website',
        url: request.original_url,
        image: image_url('default_share.png'),
        local: 'ja-JP'
      },
      twitter: {
        card: 'summary_large_image',
        site: '@https://x.com/yukimura877',
        image: image_url('default_share.png')
      }
    }
  end
end

3. application.html.erbのdisplay_meta_tagsメソッドに引数を追加する

<!DOCTYPE html>
<html>
  <head>
    <title><%= page_title(yield(:title)) %></title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <!-- meta_tag -->
    <%= display_meta_tags(default_meta_tags) %>
  </head>

4. 上記で実装完了です

  • この画像の様に、show以外のURLを共有するときに設定したデフォルトの部分が表示されます
    Image from Gyazo

終わりに

  • Dockerを使用する際に、ImageMagickの導入をHomebrewなどを使ってPCに直接導入する際は一つ注意が必要です
    なぜなら、デプロイする際にデプロイサーバー(本番環境)にはImageMagickがインストールされていないからです。MiniMagickはImageMagickがインストールされていないと使用できません
    なので、DockerFileに記述してコンテナにインストールしないと本番環境でエラーが発生すると思います
  • 以上になります。これから実装される方のお力に少しでも慣れたら幸いです
    最後までご覧いただきありがとうございました!!!

Discussion