使用无头浏览器截图生成 OG 图片
为什么 Open Graph 图片对社交分享很重要、传统方案如何生成它们,以及本站如何结合无头浏览器截图与异步队列,自动产出像素级还原的 OG 图片。
分享链接时会发生什么?
当你把一个 URL 粘贴到 Twitter、Slack 或任何即时通讯应用中时,平台会获取目标页面的 HTML,并查找 Open Graph 元标签:
如果没有 og:image,分享出去的链接就只是一个普通 URL。有了它,链接会变成包含标题、描述和视觉预览的丰富卡片。
有 OG 图片 → 点击率更高,用户一眼就能获取更多信息。
没有 OG 图片 → 链接很容易被直接划过。
生成 OG 图片的传统方式
| 方式 | 工作原理 | 优点 | 缺点 |
|---|---|---|---|
| 手动设计 | 为每个页面从 Figma/Photoshop 导出图片 | 质量高 | 难以扩展——每个页面都需要手动处理 |
| Canvas API | 在服务端通过 node-canvas 合成图片 | 可自动化 | 布局能力弱、不支持 CSS,调试痛苦 |
| @vercel/og(Satori) | 将 JSX 转换为 SVG,再转为 PNG | 与 Next.js 集成良好,可在 Edge 环境运行 | 只支持 CSS 的一部分,复杂布局能力有限 |
| 无头浏览器 | 在真实浏览器中渲染页面并截图 | 完整支持 CSS/JS,所见即所得且像素级还原 | 需要浏览器运行时,资源成本更高 |
前三种方式都在尝试模拟浏览器的渲染,而第四种方式则是直接使用浏览器本身。
本站的方案:无头浏览器截图
本站使用无头浏览器截图,但并不直接管理 Chromium 实例,而是把截图任务交给一个无头浏览器云 API。
完整的生成流程
从编辑者保存页面,到 OG 图片准备就绪,完整流程如下:
状态机
每个页面的 OG 图片生成状态遵循一个简单的有限状态机:
关键设计决策
为什么不在 Hook 中直接截图?
Hook 运行在 CMS 的请求—响应周期内,而一次截图需要 5–10 秒。如果让保存操作等待这么久,编辑体验会非常糟糕。因此 Hook 只负责标记状态和分发任务,真正的截图在异步流程中完成。
为什么需要专用的预览路由?
OG 图片需要特定的视觉布局:没有导航栏和页脚,并针对 1200×630 的宽高比进行优化。预览路由只渲染页面的内容区块,不包含网站外壳,同时通过身份验证请求头保护,避免被公开访问。
为什么 Processor 在截图前要重新加载页面?
队列任务可能会延迟执行。任务从分发到真正执行之间,编辑者可能已经继续修改了内容。重新从数据库读取页面,可以确保截图反映的是最新内容。
不同环境如何切换队列?
Dispatcher 会自动检测运行环境。在生产环境中,它使用支持重试和指数退避的云队列服务;在开发环境中,则使用内存中的 Promise Map 直接执行,无需额外的外部基础设施。