Rails × Vue.jsのSPAでTwitterのOGPを動的に反映させる方法

ご縁箱の実装で上手く情報を掴めず詰まった部分でしたため、
これからSPAで実装する方の役に立つかな？と思い、執筆いたしました。
クローラーについても知っておいた方が理解しやすいと思いましたので予備知識としてまとめています。

※ 間違っている点などございましたら、コメント欄でタコ殴りにしていただけますと幸いです。

## 目標： TwitterのOGP画像を動的に！！
ツイートのURLから出力される画像を切り替えることが出来ます。
Twitterで拡散されたいサービスを作る場合、工夫次第で大いに有効な手段になるかと思います。
![image](https://user-images.githubusercontent.com/78721963/155685673-158df3bc-4f66-4e53-a89a-8c92f5a9579d.png)

ご縁箱もおかげさまでPV数が24,000を突破しまして、
サービス紹介記事のLGTM数も100を超えました。ありがとうございます。

https://qiita.com/o83184206/items/dcab2743fea236f0aa67

それはさておき、本題に入ります。

## クローラーとは？
サイト内を巡回しているロボットです。TwitterではTwitterbot/1.0という専用のクローラーが存在します。
Twitter内でURLを貼るとTwitterクローラーはそのサイトの情報を解析し、データベースに保存します。（クローリング）

:::note info
クローラーによってサイトのmeta情報などが読み込まれることでOGPや概要文が反映されています。
:::

参考: [Twitter Developer](https://developer.twitter.com/ja/docs/tweets/optimize-with-cards/guides/getting-started)

クローラーとサーバー間の通信手段は「HTTP/HTTPSプロトコル」ですので、HTTP/HTTPSで取得できる情報は、すべてクローリングの対象となります。
(CSSファイル、JavaScriptファイル、画像、その他...)

ちなみにGoogleや、Microsoft、Yahoo!などの検索サイトを提供している企業でもロボット型検索エンジンの仕組みの中でクローラーを使用しています。


ロボット型検索エンジンとは、インターネット上にある世界中のWebサイト情報を自動的に収集して、ユーザーが検索したキーワードをもとに、収集した情報から適切だと判断されたものを検索結果として表示する仕組みです。

## TwitterクローラーがJavaScriptを実行してくれない問題


フロント側でmetaタグを動的に変更されるように実装しても、meta情報が全てのページで同じになるので、Twitterカードなどが意図通りに表示できないという問題があります。

firebaseを使えばSSRなしでOGP系サービス作れるといった例や
SSRがしやすいというNuxt.jsで実現する例を見かけましたが、
Vue.js×RailsでのSPA開発でサーバーサイドレンダリングするのは実装コストが大きいようです。

## 解決策
RailsのルーティングでTwitterBot専用の処理を記述します。
:constraintsオプションを使うと、動的セグメントのURLフォーマットを特定の形式に制限できます。
同じURL形式でも特定の条件にのみ適用されるURLを設定することが出来るというオプションです。

[参考: Railsガイド](https://railsguides.jp/routing.html#%E3%82%BB%E3%82%B0%E3%83%A1%E3%83%B3%E3%83%88%E3%82%92%E5%88%B6%E9%99%90%E3%81%99%E3%82%8B)

<br>
<br>


ご縁箱の例です。TwitterにシェアするURLとルーティングをマッチさせることと、constraintsオプションで専用のアクションを実行します。
```ruby:routes.rb
get '/:twitter_id', to: 'mylink_shares#show',constraints: { user_agent: /Twitterbot/ }

# ツイートのURLにマッチしている & リクエストのUserAgentパラメータからTwitterbotを検知した場合のみ
get '/letters/:letter_id', to: 'letter_shares#show', constraints: { user_agent: /Twitterbot/ }
```
<br>
このようにすることでツイート画面が表示される際にクローラーがツイート画面のURLにアクセスしてmeta情報などを読み込みます。

![image](https://user-images.githubusercontent.com/78721963/157118681-74cf4b0d-0e1f-471f-825a-fd051c9bff1a.png)


あとはルーティングに対応するViewファイルを用意して静的HTMLを返すのみです。


コントローラの処理でインスタンス変数を使ってデータベース保存した画像も自由に設定することも出来ます。

```ruby:app/controllers/mylink_shares_controller.rb
class MylinkSharesController < ApplicationController
  def show
    @user = User.find_by!(twitter_id: params[:twitter_id])
  end
end
```
<br>

私の場合は、[meta-tags](https://github.com/kpumuk/meta-tags)というGemを使って以下のようにmeta情報を記述しています。

```ruby:app/views/share_links/show.html.erb
<% set_meta_tags(og: { title: "#{@user.name}さんのご縁箱ページです。みんなでファンレターを書いてみましょう！",
  description: "Twitterシェアに特化したファンレター公開サービスです。ご縁箱を秒速で開設したらMyご縁箱を共有してあなたのフォロワーからファンレターを受け取ってみましょう♪",
  image: 'https://goenbako.com/img/MYLINK_OGP.jpg' },
  twitter: { card: 'summary_large_image' }) %>
```
▼ 受け取ったレターのシェア
```ruby:app/views/share_letters/show.html.erb
<% set_meta_tags(og: { title: "#{@user.name}さんに届いたファンレターです。（全文はリンクから!）",
  description: "ご縁箱を開設してファンレターを受け取ってみよう！",
  image: @share_image.image.url },
  twitter: { card: 'summary_large_image' }) %>
```

## 確認方法
開発環境ではクローラーは本番のURLにアクセスする前提のため、本番へpushする必要があります。
ローカルでは確認のしようがありませんのでご注意ください。

本番のサーバーログにはクローラーのアクセスが出力されると思ったのですが、本番のログにも反映はないため確認は本番環境にpushして自分で操作しながらか[カードバリデータ](https://cards-dev.twitter.com/validator)などを使ってみると良いかもしれません。

## おまけ (ツイート時の小技)
#### ツイート時にURLを表示させない方法
URLを最後に配置するとツイートした際の表示がスッキリします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1191858/ef8fd9b8-32e0-18ff-1973-5428bc486698.png)
個人開発したサービスにツイート機能をつけたり
普段のツイートにリンクつけたい方は押さえておいても良いのかなと思われます。個人的には右の方が好みです。

## 終わりに
結構勘違いしやすいポイントが多く困ったため、この記事が参考になれば幸いです。

よろしければご縁箱を使ってみてください。
https://goenbako.com