2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React SPA の OGP をまず固定で入れて将来の動的OGPに備える

2
Posted at

React でアプリ開発をしている中で、URLを共有したときのプレビューをちゃんと出したくなりました。

今回のアプリでは、グループURLを LINE などで共有する想定があります。

URLを共有したときに、タイトルや画像がそれっぽく出てほしい。

そこで、まずは固定の OGP を index.html に入れました。

この記事では、SPA の OGP をどう考えたか、今の暫定対応と将来の動的対応をまとめます。

React Router と Hono API で URL 共有型の投稿アプリを作る基本部分は別記事で扱っているため、ここでは共有プレビューと画像の置き場所の話に絞ります。

SPA と OGP の相性問題

React SPA は、基本的に index.html を1つ返して、ブラウザ上でルーティングします。

つまり、以下のURLがあっても、サーバーから返るHTMLは同じです。

/g/example-name
/g/another-group
/login

ブラウザで見る分には React Router が画面を切り替えてくれます。

しかし、SNS や LINE のプレビュー bot は、JavaScript の実行結果ではなく、最初に返ってきたHTMLの meta タグを見ることが多いです。

そのため、SPA で URL ごとに違う OGP を出したい場合は注意が必要です。

今回まずやったこと

今回はまだ固定グループが中心なので、まず index.html に固定 OGP を入れました。

実際のコードです。

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>同級生近況ノート</title>
    <meta
      name="description"
      content="久しぶりに会う前に、今どこで何をしているかをゆるく共有する場所です。"
    />
    <meta property="og:type" content="website" />
    <meta
      property="og:url"
      content="https://kinkyo-note.workers.dev/g/group-name"
    />
    <meta property="og:title" content="同級生近況ノート" />
    <meta
      property="og:description"
      content="久しぶりに会う前に、今どこで何をしているかをゆるく共有する場所です。"
    />
    <meta
      property="og:image"
      content="https://kinkyo-note.workers.dev/g/group-name.jpg"
    />
    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:title" content="同級生近況ノート" />
    <meta
      name="twitter:description"
      content="久しぶりに会う前に、今どこで何をしているかをゆるく共有する場所です。"
    />
    <meta
      name="twitter:image"
      content="https://kinkyo-note.workers.dev/g/group-name.jpg"
    />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

これで、group-name を共有したときに以下が出る想定です。

  • タイトル: 同級生近況ノート
  • 説明文: 久しぶりに会う前に...
  • 画像: /group-name.jpg

固定 OGP のメリット

固定 OGP のメリットは、とにかく簡単なことです。

  • Vite の index.html に書くだけ
  • 追加のAPI実装がいらない
  • 画像も public/group-name.jpg に置けば返せる
  • まず共有時の見た目を整えられる

今回のように、最初の対象グループが固定なら十分です。

固定 OGP の問題

ただし、この方法には明確な問題があります。

SPA 全体で同じ index.html を返すため、別のグループでも同じ OGP が出ます。

たとえば将来こういうURLが増えたとします。

/g/example2-name
/g/tokyo-members
/g/company-alumni

index.html に固定で書いている限り、どのURLを共有しても同じ内容になります。

title: 同級生近況ノート
image: group-name.jpg

これは、複数グループ対応を本番機能として出すなら困ります。

なぜ今回は固定でよしとしたか

今回の段階では、以下の判断をしました。

  • まだグループ作成機能は本格実装前
  • 共有したい主要URLは /g/group-name
  • まず LINE などで共有したときの見た目を整えたい
  • 動的OGPはあとで Workers 側に寄せられる

将来は Workers で動的に差し替える

複数グループに対応するなら、最終的には Workers 側で HTML を返すときに OGP を差し替えるのが自然です。

イメージはこうです。

GET /g/:slug
  -> D1 から group を取得
  -> index.html を読み込む
  -> og:title / og:description / og:image を group ごとに差し替える
  -> HTML を返す

たとえば、概念的にはこういう処理です。

app.get('/g/:slug', async (c) => {
  const slug = c.req.param('slug')
  const group = await findGroupBySlug(slug)
  const html = await getAssetIndexHtml(c)

  return c.html(
    html
      .replace('__OG_TITLE__', escapeHtml(group.name))
      .replace('__OG_DESCRIPTION__', escapeHtml(group.description))
      .replace('__OG_IMAGE__', group.ogImageUrl),
  )
})

この方式なら、React Router を使った SPA のままでも、bot に返すHTMLは URL ごとに変えられます。

動的OGPにするなら画像の置き場所も考える

今回、グループ画像は public 配下に置いています。

固定画像ならこれで問題ありません。

ただ、将来ユーザーがグループを作ったり、投稿者アイコンをアップロードしたりするなら、画像をどこかに永続保存する必要があります。

候補としては Cloudflare R2 です。

R2
  - group icon
  - user avatar
  - future uploaded images

注意点として、ブラウザの localStorage に保存した画像は、他人の端末から見えません。

つまり、OGP画像や共有されるアイコンに使うには不向きです。

共有される画像は、bot や他ユーザーがアクセスできるURLに置く必要があります。

まとめ

React SPA の OGP は、最初に返る index.html に依存します。

今回の判断はこうです。

  • 固定画像なら index.html に OGP を直書きでよい
  • 将来は Workers で /g/:slug ごとに HTML を差し替える
  • 共有される画像は localStorage ではなく、R2 など外部から見える場所に置く
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?