ワンクリックで
satori-og
Satori + @resvg/resvg-js OG image 產生最佳實踐指南。當需要產生 Open Graph 預覽圖、設定 OG/Twitter Card meta tags、或整合 Astro build-time image 產生時使用。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Satori + @resvg/resvg-js OG image 產生最佳實踐指南。當需要產生 Open Graph 預覽圖、設定 OG/Twitter Card meta tags、或整合 Astro build-time image 產生時使用。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | satori-og |
| description | Satori + @resvg/resvg-js OG image 產生最佳實踐指南。當需要產生 Open Graph 預覽圖、設定 OG/Twitter Card meta tags、或整合 Astro build-time image 產生時使用。 |
| 套件 | 版本 |
|---|---|
| satori | 0.26.0 |
| @resvg/resvg-js | 2.6.2 |
npm install satori @resvg/resvg-js
satori 0.26.0 新增:內建 JSX runtime,不再需要 React。
import satori from 'satori';
import { Resvg } from '@resvg/resvg-js';
import fs from 'node:fs';
// 全域載入字型,不要在每次請求時重新載入
const inter = fs.readFileSync('./public/fonts/Inter-Regular.ttf');
const interBold = fs.readFileSync('./public/fonts/Inter-Bold.ttf');
const FONTS = [
{ name: 'Inter', data: inter, weight: 400 as const, style: 'normal' as const },
{ name: 'Inter', data: interBold, weight: 700 as const, style: 'normal' as const },
];
const svg = await satori(
{
type: 'div',
props: {
style: { display: 'flex', color: 'white', fontSize: 48 },
children: 'Hello World',
},
},
{ width: 1200, height: 630, fonts: FONTS }
);
const resvg = new Resvg(svg, {
fitTo: { mode: 'width', value: 1200 },
font: { loadSystemFonts: false }, // satori 已嵌入字型,關掉加速
});
const png = resvg.render().asPng(); // Uint8Array
/** @jsxRuntime automatic */
/** @jsxImportSource satori/jsx */
const svg = await satori(
<div style={{ display: 'flex', color: 'white' }}>Hello</div>,
{ width: 1200, height: 630, fonts: FONTS }
);
Layout:display(flex/none)、position(relative/absolute)、所有 flexbox 屬性(flexDirection、gap 等)
Sizing:width/height/min/max(不支援 min-content/max-content/fit-content)
Typography:fontFamily、fontSize、fontWeight、fontStyle、textAlign、lineHeight、letterSpacing、textOverflow(clip/ellipsis)、textShadow、lineClamp
Visual:backgroundColor、backgroundImage(gradient/url)、borderWidth/Style/Color/Radius、boxShadow、opacity、filter、transform(2D only)
重要限制:
flex,不是 block — 每個 div 都是 flex containercalc()z-index(SVG 按文件順序繪製)// 全域載入,重複使用
const FONTS = [
{ name: 'Inter', data: fs.readFileSync('./fonts/Inter-Regular.ttf'), weight: 400, style: 'normal' },
{ name: 'Inter', data: fs.readFileSync('./fonts/Inter-Bold.ttf'), weight: 700, style: 'normal' },
];
// CJK 字型按需載入
await satori(element, {
fonts: FONTS,
loadAdditionalAsset: async (code, segment) => {
if (code === 'emoji') return `data:image/svg+xml;base64,...`;
return loadCJKFont(code);
},
});
規則:
ArrayBuffer 或 Bufferlang 屬性:<div lang="zh-TW">// 遠端圖片可以直接用,但有延遲
{ type: 'img', props: { src: 'https://...', width: 200, height: 200 } }
// 最佳實踐:預先抓取轉 base64
const avatarBase64 = Buffer.from(
await fetch(url).then(r => r.arrayBuffer())
).toString('base64');
{ type: 'img', props: { src: `data:image/jpeg;base64,${avatarBase64}`, width: 200, height: 200 } }
務必指定 width/height,否則版面不可預測。
| 平台 | 建議尺寸 | 比例 |
|---|---|---|
| Open Graph(Facebook、Threads、LinkedIn) | 1200 x 630 | ~1.91:1 |
| Twitter summary_large_image | 1200 x 628 | ~1.91:1 |
統一用 1200 x 630。
// src/pages/og/[slug].png.ts
import type { APIRoute, GetStaticPaths } from 'astro';
import satori from 'satori';
import { Resvg } from '@resvg/resvg-js';
import fs from 'node:fs';
const FONTS = [
{ name: 'Inter', data: fs.readFileSync('./public/fonts/Inter-Regular.ttf'), weight: 400 as const, style: 'normal' as const },
];
export const getStaticPaths: GetStaticPaths = async () => {
// 回傳所有需要產生 OG image 的路徑
return users.map(user => ({ params: { slug: user.username }, props: { user } }));
};
export const GET: APIRoute = async ({ props }) => {
const svg = await satori(
{ type: 'div', props: { style: { /* ... */ }, children: props.user.name } },
{ width: 1200, height: 630, fonts: FONTS }
);
const png = new Resvg(svg, {
fitTo: { mode: 'width', value: 1200 },
font: { loadSystemFonts: false },
}).render().asPng();
return new Response(png, { headers: { 'Content-Type': 'image/png' } });
};
astro build 時自動為每個 static path 產生 PNG 檔案。
<!-- Open Graph(必要) -->
<meta property="og:title" content="台灣 GitHub 開發者排行榜" />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://example.com/og/default.png" />
<meta property="og:url" content="https://example.com/" />
<!-- Open Graph(建議) -->
<meta property="og:description" content="台灣 GitHub 開發者 3D 視覺化排行" />
<meta property="og:site_name" content="GitStar" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:type" content="image/png" />
<meta property="og:locale" content="zh_TW" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="台灣 GitHub 開發者排行榜" />
<meta name="twitter:description" content="台灣 GitHub 開發者 3D 視覺化排行" />
<meta name="twitter:image" content="https://example.com/og/default.png" />
要點:
og:image:width 和 og:image:height 避免爬蟲需要抓圖片判斷尺寸summary_large_image 顯示大圖loadSystemFonts: falsePromise.all 並行(注意記憶體)flex 不是 blockcalc()、z-index、3D transformsZod v4 schema validation 最佳實踐指南。當需要定義 schema、驗證/解析 JSON 資料、type inference、或處理 unknown data 時使用。
Svelte 5 + Astro 整合最佳實踐指南。當需要建立 Svelte 元件、使用 runes API、整合 Astro islands、或用 Testing Library 測試 Svelte 元件時使用。
GitHub GraphQL API 最佳實踐指南。當需要使用 GraphQL 查詢使用者資料、處理 cursor pagination、計算 rate limit、或除錯 GraphQL errors 時使用。
gayanvoice/top-github-users 架構參考指南。當需要了解 GitHub 使用者排行榜的資料抓取管線、國家設定、排行計算邏輯、已知問題、或社群需求時使用。
Commander.js v14 CLI 框架最佳實踐。當需要建立 CLI 工具、解析命令列參數、設計 subcommands 時使用。
GitHub Actions CI/CD 最佳實踐指南。當需要設定 workflow、cron 排程、GitHub Pages 部署、使用 Octokit API、或處理 rate limiting 時使用。