Open6
Cloudflare Workersで「headタグだけSSR」を実現する
headタグだけSSRって何
headタグ部分だけサーバーサイドで生成し、body部分はCSRする
これによりサーバーの負荷を最小限にしながら動的OGPが実現できる
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<script type="module" crossorigin src="/assets/index.08ff9814.js"></script>
<link rel="stylesheet" href="/assets/index.3fce1f81.css">
<!-- ここをサーバーサイドで作る -->
<meta name="description" content="{dynamic description}" />
<meta property="og:title" content="{dynamic title}" />
<meta property="og:description" content="{dynamic description}" />
</head>
<body>
<div id="root"></div>
</body>
</html>
他の動的OGPを実現する方法と比較
headだけSSR | SSR | Dynamic Rendering | |
---|---|---|---|
サーバー負荷 | 低 | 中 | 高 |
SEO | △ | ◎ | ○※ |
※(レスポンスが遅くなってCore Web Vitals的に悪くなる可能性あり)
headだけSSRの実現方法は色々あるがなんとなく、Cloudflareが簡単そうだと思った。
プロジェクトのセットアップは公式を参考に
フロント側は適当にviteでinitしておく
npm create vite@latest
# React + TypeScriptの構成を選択
ディレクトリはこんなかんじ
head-ssr/ # Cloudflare Workersのプロジェクト
web/ # Webフロント
Cloudflare Workersの設定はこんなかんじ。
head-ssr/wrangler.toml
name = "header-ssr"
main = "src/index.ts"
compatibility_date = "2022-10-11"
[site]
bucket = "../web/dist" # ここで、Web側のビルド結果のディレクトリを指定する
公式ドキュメントを参考にすると、どうやら@cloudflare/kv-asset-handler
というパッケージが必要になるそうなのでインストール
npm i -D @cloudflare/kv-asset-handler
コードはこんなかんじ
head-ssr/src/index.ts
import {
getAssetFromKV,
serveSinglePageApp,
} from "@cloudflare/kv-asset-handler"
import manifestJSON from "__STATIC_CONTENT_MANIFEST"
/**
* Welcome to Cloudflare Workers! This is your first worker.
*
* - Run `wrangler dev src/index.ts` in your terminal to start a development server
* - Open a browser tab at http://localhost:8787/ to see your worker in action
* - Run `wrangler publish src/index.ts --name my-worker` to publish your worker
*
* Learn more at https://developers.cloudflare.com/workers/
*/
export interface Env {
// Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/
// MY_KV_NAMESPACE: KVNamespace;
//
// Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/
// MY_DURABLE_OBJECT: DurableObjectNamespace;
//
// Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/
// MY_BUCKET: R2Bucket;
__STATIC_CONTENT: KVNamespace
}
const assetFileExtensions = [
".css",
".js",
".png",
".jpg",
".jpeg",
".svg",
".gif",
]
const isAssetFileRequest = (request: Request): boolean =>
assetFileExtensions.some((ext) => request.url.endsWith(ext))
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
const asset = await getAssetFromKV(
{
request,
waitUntil(promise) {
return ctx.waitUntil(promise)
},
},
{
// ここのASSET_NAMESPACEやASSET_MANIFESTの指定を忘れないこと(2敗)
ASSET_NAMESPACE: env.__STATIC_CONTENT,
ASSET_MANIFEST: JSON.parse(manifestJSON),
mapRequestToAsset: serveSinglePageApp,
}
)
if (isAssetFileRequest(request)) return asset
// e.g. https://example.workers.dev/?id=1
const id = new URL(request.url).searchParams.get('id')
if (id === undefined) return asset
const html = await asset.text()
const ogp = generateOgpMetaTags(id)
// ここでogp関係のメタタグを注入する
return new Response(html.replace("</head>", `${ogp}</head>`), {
headers: {
"content-type": "text/html;charset=UTF-8",
},
})
},
}
function generateOgpMetaTags(id: string): string {
const description = `記事:${id}の説明`
return `
<meta name="description" content="${description}" />
<meta property="og:title" content="記事:${id}のタイトル" />
<meta property="og:description" content="${description}" />
`
}
とりあえずローカルで確認
cd web/
npm run build # フロント側を忘れずにビルドしておく
cd ../head-ssr/
wrangler dev # http://0.0.0.0:8787にアクセス
devツールのネットワークを見てみるとうまくいってるっぽい 🎉
metaタグが動的に作れるので動的OGPも実現できそう。
ちょっと話題になってるVercelのsatoriとか使ってみたい。