動的なOGPが欲しいけどSSRするほどでもない場合にやったこと
SPAのリンクをTwitterやFacebookでシェアした際に、OGPによるプレビューを表示させたいが、
SSRするほどでもない場合のやり方を考えてみた。
前提
- 完全に静的なHTML, JavaScript, CSSだけで構成されたシングルページアプリケーション。
- Next.jsのSSRなどを使っていない。
- それらの静的なファイルはNginxで配信されている。
Nginxの設定はこんな感じ。(リクエストに該当するファイルが存在しない場合は/index.htmlを返す)
server {
server_name example.com;
root /path-to-static-files;
if (!-f $request_filename) {
rewrite (.*) /index.html;
}
}
やり方
1. HTMLファイルにOGPのタグを付けて返すアプリケーションをNode.jsとExpressで作る
次のコードのような感じ。
元のアプリケーションのパスに/ogpというプレフィックスをつけたものでリクエストを受けると、
HTMLにそのページのOGPタグを付けて返す。
(プレフィックスはなんでもよい。付ける理由は後述する。)
HTMLは、どこかのファイルから読んでくる。
ここではシンプルに決まったファイルを読んでいるが、疎結合にするためにパスをヘッダーで指定したり、ネットワーク経由で読み込んでも良いと思う。
OGPタグを付ける処理は、HTMLの</head>
を<meta ... />...</head>
で置き換えるという雑な実装。
import express from 'express'
import { createServer } from 'http'
import { readFile } from 'fs/promises'
const app = express()
async function getPost(slug: string): Promise<{title: string}> { /* ... */ }
// OGP for post pages
app.get('/ogp/posts/:slug', async (req, res) => {
// OGP
const slug = req.params.slug
const html = await readFile('/path-to-static-files/index.html', 'utf-8')
const post = await getPost(slug)
res.send(html.replace('</head>', `
<meta property="og:title" content="${post.title}"/>
<meta property="og:type" content="article"/>
<meta property="og:image" content="https://example.com/image.png"/>
<meta property="og:url" content="https://example.com/posts/${slug}"/>
<meta property="og:site_name" content="My Website"/>
<meta name="twitter:card" content="summary"
</head>`))
})
// OGP for category pages
app.get('/ogp/categories/:page', async (req, res) => {
// ...
})
// default OGP
app.use('/ogp/:anything*', async (req, res) => {
// ...
})
createServer(app).listen(8000)
2. Nginxの設定を変更する
こんな感じ。
/foo/barでリクエストが来たときに、/foo/barというファイルが存在しなければ/ogp/foo/barでlocalhost:8000にプロキシしたものを返す。
server {
server_name example.com;
root /path-to-static-files;
if (!-f $request_filename) {
rewrite (.*) /ogp$1;
}
location /ogp {
proxy_pass http://localhost:8000;
}
}
これでリクエストのパスに応じてOGPのタグがついたHTMLが返ってくるようになった。万歳!
まあこのままでも問題はないが、もう一工夫してみる。
OGPの生成に多少の時間がかかる場合、このやり方では読み込みの時間が長くなり、アプリケーションのパフォーマンスに影響してしまう。
アプリケーションのユーザーにとってはOGPタグは必要ないわけだし、省略できる時は省略したい。
そこで考えたのが、クッキーを利用する方法。
フロントエンドであるクッキーに値を設定し、そのクッキーが設定されている場合は直接静的ファイルを返すのである。
3. フロントエンドでクッキーを設定する
アプリケーションのどこかに次のようなコードを入れておく。
document.cookie = `no_ogp=1; max-age=${60 * 60 * 24 * 365}`
4. Nginxの設定を再度変更する
次のように、location /ogp
の下に、if文を追加する。
no_ogp
というクッキーに空ではない値が設定されていれば、OGPタグを付ける処理をスキップして直接HTMLファイルを返す。
(rewriteの最後にlastを付けないと、rewriteしたものがproxy_passに渡ってしまう。)
server {
server_name example.com;
root /path-to-static-files;
if (!-f $request_filename) {
rewrite (.*) /ogp$1;
}
location /ogp {
if ($cookie_no_ogp) {
rewrite (.*) /index.html last;
}
proxy_pass http://localhost:8000;
}
}
はい、これで初回のリクエスト以外はOGPタグを付ける処理をスキップすることができ、アプリケーションのパフォーマンスにほぼ影響がなくなりました。
めでたしめでたし。
まとめ
- SPAにOGPタグをつけるためにNext.jsなどを使うのはかったるいので、専用のミニアプリケーションを作ってそちらでタグを付ける処理をしてみた。
- クッキーを使ってタグを付けるかどうかを制御すると、アプリケーションのパフォーマンスにも影響しない。
お読みいただきありがとうございました。
こちらは、Web上のホワイトボードアプリ: Kakeruを開発中に思いついたアイデアで、実際にアプリケーションで使用しています。
よかったらKakeruの方も使ってみてください。