在很多社交平台上一篇文章被分享出去时第一个被看到的不是标题而是那张带链接的卡片缩略图。这张图在 HTML 里由og:image这个 meta 标签决定也就是 Open Graph 图片简称 OG 图。一个认真写博客的人如果不想让自己的内容看起来像批量生产的 AI 文章通常不会忽略这张图。这篇文章来自一个略带执念的实践系列为了证明自己不是 AI先从画好一张 OG 图开始。这个标题看起来有点绕但背后的需求很具体当读者把链接发到聊天群、论坛或社交媒体时平台会抓取网页并展示标题、摘要和一张缩略图。缩略图质量直接决定点击意愿而缩略图的风格也会透露内容生产者的认真程度。这里要做的不是用 AI 绘图工具生成一张花哨封面而是用代码精确渲染一张 1200x630 的 OG 图并把这套流程沉淀成可复用脚本。你可以直接用于自己的博客、GitHub 项目介绍页或者公众号配图。下面会从 OG 图原理、SVG 模板设计、Sharp 渲染、博客接入和常见坑五个部分展开最后给出一份发布前的检查清单。1. 为什么一张 OG 图能影响内容的可信度1.1 Open Graph 协议和 og:image 的工作机制Open Graph 协议最初由 Facebook 提出用于让网页在被分享到社交平台时能提供比默认抓取更完整的标题、描述和图片。Web 开发者通过meta propertyog:title、meta propertyog:description、meta propertyog:image这三组标签告诉平台“这张页面应该如何被展示”。当一条链接被分享时抓取器会在几秒内访问页面解析 head 中相关 meta然后生成一张卡片。卡片的缩略图就是og:image指向的图片。如果这个标签缺失平台通常会自动截取页面中的第一张图或者完全不显示缩略图这会造成两种结果要么卡片样式不统一要么因自动截取导致内容错位。og:image的推荐尺寸一般是 1200x630比例约 1.91:1。这个尺寸是当前大多数社交平台的标准卡片尺寸。图片格式上PNG、JPEG 和 WebP 都能用但要注意平台兼容性。为了通用性本文使用 PNG 输出。这些细节决定了一张 OG 图是否能在不同平台正确展示。仅凭这一点就值得为博客页面专门生成一张图而不是让框架自动塞一个随机缩略图。1.2 AI 生成图片容易露出哪些马脚标题里提到“证明自己不是 AI”在 OG 图这个场景下主要针对的是越来越常见的 AI 生图封面。很多 AI 绘图模型生成的图片第一眼很漂亮但放到技术博客的分享卡片里会暴露出几个典型特征。第一是文字渲染不稳定。AI 生成图像里的标题文字经常出现拼写错误、笔画畸形或者字形风格和正文完全不一致。对技术博客来说标题是卡片最重要的信息文字一旦出错整张图的可信度立刻下降。第二是装饰元素缺乏语义。AI 生成图喜欢堆叠渐变光晕、玻璃拟态、飘带和抽象几何体这些元素看起来很“炫”但和文章内容没有关联也看不出作者的取舍。读者看到后只会觉得这是批量生成的素材不会被勾起点击欲望。第三是风格过度统一。同一个模型反复出图容易在配色、构图、光影上形成固定模式多篇文章的封面摆在一起时会给人一种“内容也是自动生成”的暗示。用代码模板生成 OG 图恰好能绕开这些问题。文字由前端模板完整控制不会出现字形扭曲版式由栅格和坐标决定稳定可复用每篇文章的标题、日期、编号和强调色不同既统一又有变化。1.3 这个系列的定位保留“作者痕迹”Going out of my way to prove Im not an AI这个标题直译是“为了证明我不是 AI我愿意多走弯路”。这是一个很有延续性的选题。第一集选择 OG 图片是因为它是最容易被读者感知到“是否用心”的页面元素。在实际项目里“作者痕迹”可以来自很多方面自绘的流程图、有个人习惯的代码注释、真实运行时的截图、对某个异常日志的手写标注。OG 图只是开始。通过用 SVG 精确控制每一个像素和字号能够传递出和 AI 生成图完全不同的信息这里有人在做决定有人在检查细节有人在为读者观看卡片时的体验负责。这种“多走弯路”的做法本质上是一种工程化实践把设计意图变成可执行的模板和脚本让每个新页面在生成时都保持同样标准。这也是本系列后续几集会持续使用的思路。2. 准备环境与项目结构2.1 Node.js 环境和依赖选择实现 OG 图生成脚本只需要一个可以运行 Node.js 的本地环境。推荐使用 Node.js 18 或更高版本因为脚本里可能会用到replaceAll等新的字符串方法也方便使用更现代的语法。先确认本地版本node -v npm -v然后创建项目目录并初始化mkdir og-poster cd og-poster npm init -y npm install sharpsharp是当前 Node.js 生态里使用最广的图像处理库底层基于 libvips。它支持读取 SVG 并输出 PNG、JPEG、WebP也能进行缩放、裁剪、旋转和压缩。对生成 OG 图来说sharp 足够用而且安装简单、跨平台稳定。这里要注意一个容易被忽略的点sharp 渲染 SVG 时依赖系统里的字体渲染能力。如果在服务器或 CI 环境生成图片必须在环境里安装中文字体否则中文标题会变成方块。关于字体问题后面第 6 节会详细讲。2.2 项目目录结构为了让生成流程具备可维护性建议一开始就按模板、脚本、数据、字体和输出目录来组织项目og-poster/ ├── templates/ │ └── og-post.svg ├── scripts/ │ └── generate.js ├── fonts/ │ └── SourceHanSansCN-Regular.otf ├── data/ │ └── posts.json ├── output/ │ └── post-001.png └── package.json每个目录的职责如下templates存放 SVG 模板。模板负责版式、配色和文字占位符。scripts存放生成脚本。脚本读取文章数据填充模板调用 sharp 输出 PNG。fonts存放字体文件。推荐把开源的思源黑体或 Noto Sans CJK 字体文件放在项目内避免依赖系统字体。data存放文章元数据比如标题、日期、文章编号、强调色。output存放生成的图片。建议输出目录加入.gitignore避免二进制文件频繁提交。把字体文件放到项目里还有一个实际好处生成结果不随运行环境变化。本地有某种字体、服务器没有导致图片风格不一致这种问题很常见。把字体固定进项目后只要脚本相同输出就一致。2.3 准备文章数据 JSON生成图片之前先把文章信息整理成结构化数据。这里以本系列第一篇文章为例{ posts: [ { slug: prove-not-ai-ep1-og-images, title: Going out of my way to prove Im not an AI – Ep. #1 – OG images, date: 2025-04-10, episode: 1, accentColor: #d97706, siteName: Code Notes } ] }各字段含义slug文章链接的最后一段用作输出文件名。title要显示在 OG 图上的文章标题可能需要按行拆分。date文章发布日期。episode系列第几集用于生成EP 01标签。accentColor强调色控制卡片上的标题下划线、标签底色等视觉元素。siteName博客名称显示在卡片左上角或底部。使用 JSON 而不是直接在脚本里写字符串是为了后续能循环生成多张图。写一篇文章时只需要新增一条数据脚本不用改。这里需要补充说明实际数据可按自己博客的 front matter 或 CMS 导出格式调整。如果文章标题有特殊符号比如–和#在 JSON 里是合法字符但放进 URL 或文件名前需要处理。本示例中.replaceAll不会处理 HTML 转义如果标题包含、、在 SVG 中要转义。这一点会在第 4 节代码里体现。3. 设计一张“不像 AI 生成”的 SVG 模板3.1 版式原则克制、对齐、清晰的信息层级生成 OG 图之前先想清楚版式。1200x630 的卡片不算大在聊天列表或信息流里缩略显示时细节几乎看不清。因此设计要遵循三个原则背景简洁、标题突出、信息分层明确。背景不需要复杂渐变。纯白、浅灰或带一点纸张质感的底色搭配一种强调色反而更有“人工排版”的感觉。标题是卡片的绝对主角要占据上半部分或左半部分字体端正留出足够呼吸空间。置底可以放发布日期、文章编号和博客名字号比标题小一个量级。不要同时叠加阴影、描边、渐变和多种装饰形状。每多加一个视觉元素就要在读者脑海里增加一次认知负担。代码生成图片的真正优势是可以用规格保证每张图都在同一套克制体系里避免手忙脚乱地临时加元素。3.2 手写一张基础 SVG 模板先创建templates/og-post.svg。下面是一个适合程序填充的模板使用{{siteName}}、{{titleLines}}、{{date}}、{{episode}}、{{accentColor}}作为占位符svg xmlnshttp://www.w3.org/2000/svg width1200 height630 viewBox0 0 1200 630 rect width1200 height630 fill#faf9f5/ rect x80 y80 width80 height10 fill{{accentColor