# 构建时生成 OG 图像的提示

> AI 代理提示：在 Astro 或 Next.js 中使用 Satori 和 sharp 在构建时为每个页面生成 Open Graph 图像，无需外部服务。

**Type:** Prompt  
**Tools:** Cursor, Claude Code, Codex, Windsurf  
**Stack:** Astro, Next.js, TypeScript  
**Difficulty:** medium  
**Updated:** 2026-06-08

---

向您的代理提供此提示，以使用 Satori 和 sharp 在构建时为每个页面生成唯一的 OG 图像 PNG — 避免运行时 OG 图像服务（如 Vercel OG）的延迟和成本。

## 主要提示

```txt title="Main Prompt"
You are working in an existing Astro 5 static site with TypeScript.

Task: generate a static Open Graph PNG image for every blog post at build time.

Requirements:
- Install `satori` and `sharp`.
- Create `src/lib/generate-og.ts` that exports `generateOgImage({ title, description }: OgData): Promise<Buffer>`.
  - Use Satori to render a JSX template (800 x 420 px) with: site name top-left, post title
    (large, bold, white), description (smaller, gray), a solid dark background (#0f172a).
  - Convert the Satori SVG output to PNG using `sharp(Buffer.from(svg)).png().toBuffer()`.
  - Use only system-safe fonts or load `src/fonts/Inter-Bold.ttf` (create a note to add the file).
- Create `src/pages/og/[slug].png.ts` as an Astro endpoint:
  - `getStaticPaths`: call `getCollection('blog')` and return a path per post.
  - `GET`: call `generateOgImage` with the post's title and description, set
    `Content-Type: image/png`, return the buffer.
- In the blog post layout, set `<meta property="og:image" content={ogImageUrl} />` where
  `ogImageUrl` is constructed as `/og/${post.slug}.png`.
- Do NOT use `@vercel/og`, `next/og`, or any cloud image generation service.
- Do NOT use Canvas or puppeteer.

Stop and list all planned file changes before writing any code.
```

## 实现说明

- Satori 接受类 JSX 对象（React 元素语法），但**不**使用 React 运行时。直接通过 `satori` 的第一个参数传递纯对象树。
- 字体加载：Satori 需要至少一种字体。在构建时使用 `fs.readFileSync` 加载 `.ttf` 文件 — 这在 Astro 静态构建中没问题。
- 生成的 PNG 会位于 `astro build` 后的 `dist/og/` 文件夹中。检查其大小；使用纯色背景时，800x420 的 PNG 应小于 100 KB。
- 如果是 Next.js 而非 Astro：在 `app/og/[slug]/route.ts` 中使用路由处理程序，并用 PNG 缓冲区调用 `NextResponse`。

## 预期文件更改

```txt
src/lib/generate-og.ts              (new)
src/pages/og/[slug].png.ts          (new — Astro endpoint)
src/layouts/BlogPost.astro          (edited — add og:image meta)
src/fonts/Inter-Bold.ttf            (add manually — not auto-generated)
package.json                        (edited — add satori, sharp)
```

## 验收标准

- `astro build` 为每篇博客文章在 `dist/og/` 下生成一个 `.png` 文件。
- 每个 PNG 为 800 x 420 像素。
- 博客文章布局包含指向正确路径 `/og/<slug>.png` 的 `og:image`。
- 在浏览器中打开 OG 图像 URL 会显示带有文章标题的样式卡片。

## 测试命令

```bash
bun add satori sharp
bun run build
ls dist/og/
file dist/og/my-first-post.png   # should output "PNG image data, 800 x 420"
curl -I http://localhost:4321/og/my-first-post.png | grep content-type
```

## 常见 AI 错误

- 导入 React 并尝试使用 `ReactDOMServer` 渲染 — Satori 不使用 React 运行时。
- 忘记加载字体，导致 Satori 抛出 `"No font found for the first character"`。
- 将 `og:image` 设置为相对路径如 `./og/slug.png` — 在生产环境中必须是绝对 URL。
- 使用 `canvas` 包，该包需要原生绑定，与许多 CI 环境不兼容。

## 修复提示

```txt title="Fix Prompt"
Satori throws a font error or the PNG is blank. Fix in order:
1. Add font loading: `const fontData = fs.readFileSync('src/fonts/Inter-Bold.ttf'); fonts: [{ name: 'Inter', data: fontData, weight: 700 }]` in the satori call.
2. Make sure the JSX-like element passed to satori is a plain object, not a JSX expression — call `satori(element, options)` directly.
3. For the og:image meta tag, use an absolute URL: `const base = import.meta.env.SITE; ogImageUrl = new URL(\`/og/\${slug}.png\`, base).toString();`
Show only the corrected diff.
```