在社群平台上分享連結時,你是否也常被預覽圖的設計卡住? 預設封面太普通、自製圖片太麻煩、還要配合文章動態變化……這些問題,我以前也都遇過。
直到我發現了 Vercel 推出的開源工具 Satori —— 一個能用 React 語法(JSX)直接產出 SVG 的圖像引擎,支援在 Next.js、Astro、Hono 等框架中動態生成高質感 Open Graph 圖像。不只效能快、成本低,還能自訂字體、主題、甚至配合 Markdown 自動化產圖。
本篇文章會從原理、實作到最佳化策略,手把手帶你用程式碼打造屬於自己風格的 OG 圖像預覽系統。讓你從此不再為「分享出去長什麼樣」而苦惱,也讓社群連結變得更吸睛!
什麼是 Satori
Satori 是 Vercel 創建的一個開源庫,專門用於將 HTML 和 CSS 轉換為 SVG 圖像。它的名稱來源於日語「悟り」,意為啟發
或理解
。Satori 的主要用途是生成動態的 Open Graph 圖像,這些圖像可用於社交媒體分享時顯示的預覽卡片。
Satori 支持 JSX 語法,這使得定義圖像內容變得非常直觀。它處理佈局計算、字體渲染、排版等工作,生成的 SVG 與瀏覽器中的 HTML 和 CSS 呈現效果完全匹配。基本用法示例:
import satori from 'satori';
const svg = await satori(
<div style={{ color: 'black' }}>
hello, world
</div>,
{
width: 600,
height: 400,
fonts: [
{
name: 'Roboto',
data: robotoArrayBuffer,
weight: 400,
style: 'normal',
},
],
}
);
這段代碼會生成一個包含黑色文字”hello, world”的 SVG,尺寸為 600×400。
誰在使用 Satori
Satori 的應用範圍廣泛且多樣化。Vercel 作為開發者將其整合到 @vercel/og
庫中,為 Next.js 應用提供動態 OG 圖像生成功能。個人部落格開發者利用 Satori 自動為文章生成 OG 圖像,展示標題、作者和閱讀時間等元數據。Cloudflare Pages 則提供了 Vercel OG 插件,讓開發者能在其平台上輕鬆創建動態 OG 圖像。此外,越來越多的企業正採用 Satori 為產品頁面、文章和活動生成引人注目的社交媒體預覽圖像,充分展現了 Satori 在各種網絡應用場景中的實用性和靈活性。
為什麼選擇 Satori
Satori 相比傳統的 OG 圖像生成方法有幾個明顯的技術優勢:
-
高性能:Vercel 聲稱,與傳統的基於無頭瀏覽器(如 Chromium)的方法相比,Satori 的生成速度快 5 倍。它不需要啟動整個瀏覽器實例,而是直接將 HTML/CSS 轉換為 SVG。
-
Edge 兼容:Satori 可以在 Edge 運行時環境中運行,如 Vercel Edge Functions 和 Cloudflare Workers,這使得圖像生成可以更接近用戶,減少延遲。
-
成本效益:在 Edge Functions 中運行 Satori 比在無服務器函數中運行 Chromium 便宜約 160 倍。
-
不依賴 DOM:Satori 不依賴於瀏覽器的 DOM,這意味著它可以在服務器端或其他沒有瀏覽器環境的地方運行。
-
自動緩存:
@vercel/og
會自動添加適當的標頭,以在 Edge 上緩存計算生成的圖像,幫助減少成本和重複計算。
在不同環境中的整合示例
在 Astro 中使用 Satori
在 Astro 中,你可以使用 satori-html
包來處理 HTML 模板字符串:
import type { APIContext } from "astro";
import { html } from "satori-html";
import satori from "satori";
import { readFileSync } from "fs";
export async function GET({ params }: APIContext) {
const fontData = readFileSync("./public/fonts/Roboto-Regular.ttf");
const markup = html(`
<div
style="height: 100%; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: white; font-family: 'Roboto';"
>
<div style="display: flex; font-size: 60px; font-weight: 700;">
Hello, Astro!
</div>
</div>
`);
const svg = await satori(markup, {
width: 1200,
height: 630,
fonts: [
{
name: "Roboto",
data: fontData,
weight: 400,
style: "normal",
},
],
});
return new Response(svg, {
headers: {
"Content-Type": "image/svg+xml",
"Cache-Control": "public, max-age=31536000, immutable",
},
});
}
這個方法允許在 Astro 的伺服器端點中生成動態 OG 圖像。
在 Hono.js 與 Next.js 整合中使用
對於使用 Hono.js 和 Next.js 的項目,你可以在 API 路由中集成 Satori:
// app/api/[...route]/route.tsx
import { Hono } from 'hono';
import { handle } from 'hono/vercel';
import satori from 'satori';
import { readFileSync } from 'fs';
const app = new Hono();
app.get('/api/og', async (c) => {
const fontData = readFileSync('./public/fonts/Roboto-Regular.ttf');
const svg = await satori(
<div style={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 48,
background: '#f6f6f6',
color: '#333',
fontFamily: 'Roboto',
}}>
Generated with Hono and Satori
</div>,
{
width: 1200,
height: 630,
fonts: [
{
name: 'Roboto',
data: fontData,
weight: 400,
style: 'normal',
},
],
}
);
return c.body(svg, {
headers: {
'Content-Type': 'image/svg+xml',
'Cache-Control': 'public, max-age=31536000, immutable',
}
});
});
export const GET = handle(app);
export const POST = handle(app);
這個示例展示了如何在 Hono.js 和 Next.js 集成的環境中使用 Satori。
注意事項和調試技巧
在使用 Satori 時,有一些常見的陷阱和限制需要注意:
JSX 兼容性問題
在某些環境中,直接使用 JSX 可能會遇到問題。解決方案包括:
- 使用
satori-html
來處理 HTML 模板字符串(如 Astro 示例中所示) - 使用非 JSX 語法:如果沒有啟用 JSX 轉譯器,你可以直接傳遞類似 React 元素的對象:
await satori(
{
type: 'div',
props: {
children: 'hello, world',
style: { color: 'black' },
},
},
options
)
這個問題可以通過使用 React 的 createElement
方法來解決。React.createElement 是 React 庫的核心方法之一,它允許開發者以編程方式創建 React 元素,而不需要使用 JSX 語法。這種方法特別有用於那些不支持或不想使用 JSX 的環境中。通過直接調用 createElement,你可以構建出與 JSX 等效的元素結構,從而繞過 JSX 轉換的步驟,同時保持代碼的可讀性和功能性。這種技術不僅解決了特定環境下的兼容性問題,還為開發者提供了更底層的 React 元素操作方式。
字體渲染問題
字體渲染是 Satori 中最常見的問題之一:
-
缺少字體:Satori 默認只包含 Noto Sans 字體。對於其他字體,你需要手動加載字體文件。
-
缺少字形:某些字符(特別是中文、日文等非拉丁字符)可能會因為字體缺少相應的字形而不顯示。
// 支持中文字體的示例
const notoSansSCData = await fs.promises.readFile('./NotoSansSC-Regular.ttf');
const svg = await satori(
你好,世界!,
{
width: 600,
height: 400,
fonts: [
{
name: 'Noto Sans SC',
data: notoSansSCData,
weight: 400,
style: 'normal',
},
],
}
);
某些字符在中文和日文中有不同的字形,但它們的 Unicode 是相同的。如果你需要同時顯示中文和日文字符,可能需要使用專門的字體。
調試消失的元素
如果元素不顯示或”消失”,可能是由於以下原因:
-
CSS 限制:Satori 只支持 CSS 的一個有限子集。
-
字體問題:如前所述,確保加載了正確的字體和字形。
-
命名或版本控制:有時,在複製和重命名元素後,它們可能會”消失”。這可能是由於撤銷操作或隱藏的命名衝突。
啟用調試模式可以幫助解決這些問題:
const svg = await satori(
Your content here,
{
width: 600,
height: 400,
fonts: [...],
debug: true, // 啟用調試模式
}
);
高級使用想法
主題切換(明/暗模式)
為不同的主題提供不同版本的 OG 圖像:
const generateOGImage = async (theme = 'light') => {
const isDark = theme === 'dark';
const svg = await satori(
<div style={{
background: isDark ? '#1a1a1a' : '#ffffff',
color: isDark ? '#ffffff' : '#1a1a1a',
// ... 其他樣式
}}>
<h1>我的文章標題</h1>
<p>作者:Jane Doe</p>
</div>,
{
width: 1200,
height: 630,
fonts: [...],
}
);
return svg;
};
// 使用
app.get('/api/og', async (c) => {
const theme = c.req.query('theme') || 'light';
const svg = await generateOGImage(theme);
// ... 返回 SVG
});
從 Markdown frontmatter 自動生成 OG 圖像
在像 Next.js 或 Astro 這樣的框架中,你可以從 Markdown 文件的 frontmatter 數據自動生成 OG 圖像:
// 假設你有一個處理 Markdown 的函數
import { getPostBySlug } from '../lib/posts';
export default async function handler(req, res) {
const { slug } = req.query;
const post = getPostBySlug(slug);
const svg = await satori(
<div style={{
// ... 樣式
}}>
<h1>{post.title}</h1>
<p>作者:{post.author}</p>
<p>發布於:{new Date(post.date).toLocaleDateString()}</p>
{post.coverImage && (
<img
src={`data:image/jpeg;base64,${post.coverImageBase64}`}
style={{ width: 300, height: 200, objectFit: 'cover' }}
/>
)}
</div>,
{
width: 1200,
height: 630,
fonts: [...],
}
);
// 返回 SVG
res.setHeader('Content-Type', 'image/svg+xml');
res.send(svg);
}
CDN 緩存策略
為了提高性能和減少生成成本,實施有效的 CDN 緩存策略至關重要:
export default async function handler(req, res) {
// ... 生成 SVG
// 設置緩存標頭
res.setHeader('Cache-Control', 'public, max-age=86400, s-maxage=604800, stale-while-revalidate=31536000');
res.setHeader('Content-Type', 'image/svg+xml');
res.send(svg);
}
Vercel 的 @vercel/og
會自動添加適當的標頭,在 Edge 上緩存計算生成的圖像,幫助減少成本和重複計算。
結論
Satori 提供了一個高效、靈活的解決方案,用於在現代前端框架中生成動態 OG 圖像。與傳統的基於瀏覽器的方法相比,它擁有顯著的性能和成本優勢。無論你使用的是 Next.js、Astro、Cloudflare Pages 還是 Hono.js,Satori 都能幫助你創建高質量的社交媒體預覽圖像。
隨著社交媒體分享在數字營銷中的重要性不斷增長,像 Satori 這樣的工具將變得越來越重要。通過本文介紹的技術和示例,你應該能夠在自己的專案中輕鬆集成 Satori,並開始創建引人注目的動態 OG 圖像。