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

React アプリのリンクをサクッと Twitter で魅せる

More than 1 year has passed since last update.

Twitterで魅せる とは

image.png
こういうやつ! 名前は Twitter Card 。
リンクが Twitter 上でどう表示されるかは、Card Validator | Twitter Developersで確認できる。

これを Reactアプリのリンクで表示したい! という話。
最近初めて React を触っていざ Twitter Card 対応しようと調べた際、SPA における OGP 対応の悩みを知りました。

対応方法はいくつかありますが、今回は筆者が実際に試した2つの方法を簡単に書きたいと思います。
実際に採用した「Static Site Generator の方法」 と 「Cloud Functions で meta タグを差し替える方法」の2つです。

方法1: SSG

Server Side Rendering (SSR) と比較して、Static Site Generator (SSG) と呼ぶようです。
事前に JS を実行し各ページの HTML を生成してそれを配信する手法のこと。

筆者は小規模な個人アプリについてこの方法で OGP 対応しました。
具体的には react-snap + react-helmet で HTML を生成し、それを Firebase Hosting で配信しています。1

react-snap

ローカルでクロールして HTML を生成してくれるライブラリ。
(名前に react って入ってるけど、別に react 専用のライブラリではなく Vue でも使えます!)
image.png

package.json の scripts に "postbuild": "react-snap" を足すだけでよしなにやってくれる!

package.json
"scripts": {
  "start": "react-scripts-ts start",
  "build": "react-scripts-ts build",
  "postbuild": "react-snap",
  "test": "react-scripts-ts test --env=jsdom",
  "eject": "react-scripts-ts eject"
}

これを使って手元で事前にレンダリングした結果を HTML として持つことができます。
ただ、これだけではどのページも同一の meta タグ (ルートのindex.htmlに書いたもの) になってしまいます。
そこで、react-helmet を導入してページ別に meta タグを設定するようにします。

react-helmet

hello.tsx
import { RouteComponentProps } from 'react-router-dom'
import { Helmet } from "react-helmet";

export const Hello: React.SFC<RouteComponentProps<{}>> = props => (
  <div> 
    <Helmet
      title={'Hello World'}
      meta={[
        { name: 'twitter:card', content: 'summary' },
        { property: 'og:image', content: 'path/to/og_image' },
        { property: 'og:title', content: 'Helloページ' },
        { property: 'og:description', 'サンプルページです' },
        { property: 'og:url', content: `hoge_domain${props.path}` }
      ]}
    />
    <div> Hello! </div>
  </div>
)

こんな感じで Helmet コンポーネントを挿しこみ、meta 情報を指定できる。

この helemt を導入した上で yarn build すると、生成される HTML の meta タグは確かに動的に生成できている。
build/コンポーネント名/index.html を見るとこんな感じになってて嬉しい!

build/Hello/index.html
<meta content="Helloページ" data-react-helmet="true" property="og:title">

Firebase にデプロイ

いたってノーマルなままの設定でデプロイするだけ。

firebase.json
{
  "hosting": {
    "public": "build",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ]
  }
}

こんな感じでサクッといけちゃいました!

方法2: Cloud Functions による meta タグ差し替え

こちらについてはたくさんの記事があるので簡単に。
最初はこの方法で作ったのですが、小規模な個人開発かつシンプルなことしかしないアプリだったので、方法1の方が適していると考え切り替えました。

Cloud Functions による動的コンテンツの配信 にも書いてあるように、meta タグ差し替えをすることで OGP 対応できます。

SEO を向上させる単一ページアプリを事前レンダリングする。これにより、さまざまなソーシャル ネットワーク間で共有できる動的な meta タグを作成できます。

Cloud Functions に TypeScript を使う を参考にセットアップし、こんな感じで meta タグを差し替えます。

functions/index.tsx
import * as functions from 'firebase-functions'
import * as fs from 'fs'

export const PreRender = functions.region('asia-northeast1').https.onRequest((req, res) => {
  res.set('Cache-Control', 'public, max-age=300, s-maxage=600')
  fs.readFile('./index.html', 'utf8', (e, html) => {
    const responseHtml = html.replace('トップページ', 'Helloページ') // meta タグの中身を置換する処理を書く
    res.status(200).send(responseHtml)
  })
})

そして、Firebase Hosting のリライトを使って、リクエストを Cloud Functions に渡します。

firebase.json
{
  "hosting": {
    "rewrites": [
      {
        "source": "hello/*",
        "function": "PreRender"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

おわりに

だいぶざっくりと書きましたが、参考になれば幸いです。

参考

公式ドキュメント

ブログなど

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