diff options
| author | Yasutake Yohei <yohei@yasutakeyohei.com> | 2026-07-04 23:20:31 +0900 |
|---|---|---|
| committer | Yasutake Yohei <yohei@yasutakeyohei.com> | 2026-07-04 23:20:31 +0900 |
| commit | 38f811a24e9611c53105931399a4de9625923817 (patch) | |
| tree | c9185af11f8cb136345bb020867157bfc9c6c814 | |
| parent | 055a3432d87a76bf0e0187a47c51e09c81a269f0 (diff) | |
og画像: タイトルハッシュによるキャッシュで再生成をスキップ
| -rw-r--r-- | src/pages/og/[slug].png.ts | 37 |
1 files changed, 36 insertions, 1 deletions
diff --git a/src/pages/og/[slug].png.ts b/src/pages/og/[slug].png.ts index 2b95898..1047647 100644 --- a/src/pages/og/[slug].png.ts +++ b/src/pages/og/[slug].png.ts @@ -1,6 +1,12 @@ import satori from "satori"; import { Resvg } from "@resvg/resvg-js"; -import { readFileSync, existsSync, readdirSync } from "node:fs"; +import { + readFileSync, + existsSync, + readdirSync, + writeFileSync, + mkdirSync, +} from "node:fs"; import path from "node:path"; const contentDir = "src/content/docs"; @@ -136,6 +142,29 @@ export async function GET({ params }: { params: { slug: string } }) { title = `${reiwa}${month}定例会`; } + // Check cache: if title hash matches, return cached PNG + const distDir = path.join(process.cwd(), "dist", "og"); + const cacheFile = path.join(distDir, `${slug}.png`); + const cacheJsonPath = path.join(distDir, ".og-cache.json"); + + let cache: Record<string, string> = {}; + if (existsSync(cacheJsonPath)) { + cache = JSON.parse(readFileSync(cacheJsonPath, "utf-8")); + } + + const { createHash } = await import("node:crypto"); + const titleHash = createHash("md5").update(title).digest("hex"); + + if (existsSync(cacheFile) && cache[slug] === titleHash) { + const cachedBuffer = readFileSync(cacheFile); + return new Response(cachedBuffer, { + headers: { + "Content-Type": "image/png", + "Cache-Control": "public, max-age=31536000, immutable", + }, + }); + } + const svg = await satori( { type: "div", @@ -264,6 +293,12 @@ export async function GET({ params }: { params: { slug: string } }) { const resvg = new Resvg(svg, { fitTo: { mode: "width", value: 1200 } }); const pngBuffer = resvg.render().asPng(); + // Save to cache + if (!existsSync(distDir)) mkdirSync(distDir, { recursive: true }); + writeFileSync(cacheFile, pngBuffer); + cache[slug] = titleHash; + writeFileSync(cacheJsonPath, JSON.stringify(cache, null, 2)); + return new Response(pngBuffer, { headers: { "Content-Type": "image/png", |
