Skip to main content

satori-og

Satori + @resvg/resvg-js OG image 產生最佳實踐指南。當需要產生 Open Graph 預覽圖、設定 OG/Twitter Card meta tags、或整合 Astro build-time image 產生時使用。

Ir a la instalación

Datos de origen

Repositorio
recca0120/codeatlas
Última actividad en el origen
27 de marzo de 2026 a las 20:32
Idioma detectado de SKILL.md
chino
Estrellas
1
Forks
0

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
satori-og
description
Satori + @resvg/resvg-js OG image 產生最佳實踐指南。當需要產生 Open Graph 預覽圖、設定 OG/Twitter Card meta tags、或整合 Astro build-time image 產生時使用。
# Satori + resvg OG Image Generation Guide ## 版本資訊 | 套件 | 版本 | |------|------| | satori | 0.26.0 | | @resvg/resvg-js | 2.6.2 | ```bash npm install satori @resvg/resvg-js ``` **satori 0.26.0 新增**:內建 JSX runtime,不再需要 React。 ## 基本用法 ### 方式一:Plain objects(無需 transpiler) ```typescript 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 ``` ### 方式二:內建 JSX runtime(0.26.0+) ```tsx /** @jsxRuntime automatic */ /** @jsxImportSource satori/jsx */ const svg = await satori( <div style={{ display: 'flex', color: 'white' }}>Hello</div>, { width: 1200, height: 630, fonts: FONTS } ); ``` ## 支援的 CSS 子集 **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) **重要限制**: - **預設 display 是 `flex`**,不是 `block` — 每個 div 都是 flex container - 不支援 `calc()` - 不支援 `z-index`(SVG 按文件順序繪製) - 不支援 3D transforms - 不支援 WOFF2 字型 — 只支援 TTF、OTF、WOFF ## 字型載入 ```typescript // 全域載入,重複使用 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); }, }); ``` **規則**: - 只支援 TTF、OTF、WOFF(不支援 WOFF2) - 字型資料必須是 `ArrayBuffer` 或 `Buffer` - CJK 文字加 `lang` 屬性:`<div lang="zh-TW">` ## 圖片處理 ```typescript // 遠端圖片可以直接用,但有延遲 { 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**,否則版面不可預測。 ## OG Image 尺寸標準 | 平台 | 建議尺寸 | 比例 | |------|----------|------| | Open Graph(Facebook、Threads、LinkedIn) | **1200 x 630** | ~1.91:1 | | Twitter summary_large_image | 1200 x 628 | ~1.91:1 | **統一用 1200 x 630**。 ## Astro 整合(Build-time 產生) ```typescript // 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 檔案。 ## Meta Tags 設定 ```html <!-- 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" /> ``` **要點**: - Image URL 必須是絕對路徑 - 指定 `og:image:width` 和 `og:image:height` 避免爬蟲需要抓圖片判斷尺寸 - Twitter 用 `summary_large_image` 顯示大圖 - 用 PNG 格式(文字較清晰) ## 效能建議(大量產生時) 1. 字型全域載入一次,不要每次重新讀取 2. 遠端圖片預先 fetch 轉 base64 3. resvg 設定 `loadSystemFonts: false` 4. Build time 產生(SSG)而非 on-demand 5. 內容未變則跳過重新產生 6. JSX 結構保持簡單,深層巢狀會拖慢 7. 多張圖片用 `Promise.all` 並行(注意記憶體) ## 常見陷阱 1. 預設 display 是 `flex` 不是 `block` 2. 不支援 WOFF2 字型 3. 不支援 `calc()`、`z-index`、3D transforms 4. img 未指定 width/height 導致版面異常 5. 遠端圖片網路問題導致 build 失敗 → 預先 fetch + fallback
Ver en GitHub