Posted at

fly.ioでSPA&OGP芸を最速かつゼロ依存でやる(package.jsonすら不要)

個人開発界隈ではOGP芸というものが流行っているみたいですね。

SNSにシェアする際に表示されるOGPの設定や画像をいい感じにするみたいなことのことですが、SPAではこれを生成するのが割と面倒だったりします。

自分が作っているg4でもOGPを使ったシェアを実装していますが、SSRを使って配信していて、割と設定が面倒だったりしています。

こんなの

image.png

今回、なるべくSSRをしないでSPAのままやってみることはできないかというのをfly.ioで試してみました。


作ったものの概要


  • 実際にOGP画像として表示される画像は/image.png?i=0で配信する。


  • /?i=0のようなパラメータをアクセスすると、入力した↑を含むOGPが反映されたhtmlを表示する

  • fly.ioが日本語に対応してないので、titleに日本語は使えない

  • せっかくfly.ioを使ってますが、今回はOGPだけにフォーカスしたいのでキャッシュ周りは実装してません


作ったもの

https://og-image-sample.edgeapp.net?i=0

or

https://og-image-sample.edgeapp.net?i=1

これをtwitterやfacebookなどでシェアしてみてください。


作り方

事前にflyのcliをPCにインストールしておく

npmは必要ないです(あったほうが便利だけど最速なので)

必要なファイルは4つだけ

- index.js: なんか処理書くやつ

- template.html: テンプレートです。ここに情報を流し込む

- fly.yml: なんか設定書くやつ

- sample.svg: OGP芸したい適当なsvg。今回はURLによって内容を書き換えるため雑に{{title}}みたいなやつをここに書いて、テキスト置換でそこに内容を埋め込んでます。


template.html

<!DOCTYPE html>

<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="theme-color" content="#000000" />
<title>fly.ioで最速ogp芸</title>
<meta name="description" content="by shwld" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:creator" content="@shwld" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://www.g-g-g-g.games" />
<meta property="og:title" content="shwld" />
<meta property="og:description" content="shwld" />
<meta property="og:image" content="https://www.g-g-g-g.games/assets/image.png" />
<meta property="og:image:alt" content="shwld" />
</head>
<body>
<h1>fly.ioでogp芸をする場合のサンプルだよ</h1>
</body>
</html>

templateはこんな感じになりました。

ogpのcontentは置き換えるので何でもいいです。とりあえずさり気なくg4のものを入れてアピールしておきます。

次にindex.jsです。こちらにすべての処理を書きます。


index.js

import { Image } from '@fly/image'

import { mount } from "@fly/fetch/mount"

// templateをテキストで取得するFunction
async function getTemplate() {
const resp = await fetch("file://src/template.html")
return await resp.text()
}

// svgをテキストで取得しつつ中身ちょっと埋め込めるFunction
async function getSvgText(title) {
const resp = await fetch("file://src/sample.svg")
const text = await resp.text()
// g4(https://www.g-g-g-g.games)ではReactでテキスト化されたsvgを吐いてるが、とりあえず単純に置換する
return text.replace('{{content}}', title)
}

// 画像形式のレスポンス作るFunction
async function responseImage(svgText) {
const svgResp = new Response(Buffer.from(svgText))
const buf = await svgResp.arrayBuffer()
const png = new Image(buf).png()
const result = await png.toBuffer()
return new Response(result.data, {
headers: {
'Content-Type': 'image/png',
'Content-Length': result.data.byteLength.toString(),
}
})
}

// 出力データのパターン
const TITLES = [
'g4 is pomodoro rpg!',
'fly.io de OGP!!',
]

// fly.ioのrouterみたいなやつ。ここに処理を書いてく。
const mounts = mount({

// このパスでogpの画像を生成する
'/image.png': async (req, init) => {
const url = new URL(req.url)

// URLからQueryStringを取得
const index = url.searchParams.get('i')
if (index !== '0' && index !== '1') {
return new Response('not found', { status: 404 })
}

// 対応したタイトルを取得
const title = TITLES[index]

// タイトルをsvgに埋め込んだテキストを作る
const svgText = await getSvgText(title)

// svgからpng画像を生成する
return responseImage(svgText)
},

// このパスをシェアする
'/': async (req, init) => {
const url = new URL(req.url)

// URLからQueryStringを取得
const index = url.searchParams.get('i')
if (index !== '0' && index !== '1') {
return new Response('not found', { status: 404 })
}

// 対応したタイトルを取得
const title = TITLES[index]

// テンプレートをparseして編集できるようにする
const doc = Document.parse(await getTemplate())

// テンプレートにOGPを埋め込む
doc.querySelector('meta[name="description"]').setAttribute('content', title)
doc.querySelector('meta[property="og:url"]').setAttribute('content', url.href)
doc.querySelector('meta[property="og:title"]').setAttribute('content', title)
doc.querySelector('meta[property="og:description"]').setAttribute('content', title)
doc.querySelector('meta[property="og:image"]').setAttribute('content', `${url.origin}/image.png?i=${index}`)
doc.querySelector('meta[property="og:image:alt"]').setAttribute('content', title)

// htmlを返す
return new Response(doc.documentElement.outerHTML, {
headers: { 'Content-Type': 'text/html' },
status: 200,
})
},
})

// リクエストをmountsの定義を使って処理するように設定する
fly.http.respondWith(mounts)


index.jsはこんな感じ。

/template.html のogpを書き換えて出すだけ。

/image はsvgファイルを読み込んで文字列を置換したものをpngに変換して返してます。

fly.ymlやsvgファイルは特に特別な設定はないのでドキュメントや最後にソースを貼るのでそちらを見ていただければ。

あとは、fly.ioのコンソールでアプリを作って、fly deploy するだけ。

再度になりますが、

https://og-image-sample.edgeapp.net?i=0

or

https://og-image-sample.edgeapp.net?i=1

これをtwitterやfacebookなどでシェアしてみてください。

以上になります。ソースはこちらにあります。