关于 seo-blog-images
用于为博客文章制作并集成符合 SEO 要求的图片的工作流技能。它按用途选择截图、确定性图表或生成式视觉内容,并检查图片文件与元数据。
主要功能
- 01为每个图片位置选择视觉形式
- 02生成或压缩图片文件
- 03根据真实数值制作图表
- 04检查图片中的文字错误
- 05添加 SEO 元数据和尺寸属性
适用场景
- 01制作博客文章封面图
- 02为教程添加产品截图
- 03用图表呈现基准测试数据
- 04审查图片文件名和加载设置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ illyism/ seo-skills --skill seo-blog-images安装后在 Claude Code 中输入/seo-blog-images确认已加载。
详细介绍
博客图片与图片 SEO
默认的 AI 博客配图会损害文章效果:深色背景上有 5 个发光的线框图标、信息图标签乱码、一个名为 image-1.png 的 2.4 MB PNG,而且没有 width/height,导致页面加载时布局跳动。此工作流会为每个位置选择合适的视觉类型,制作图片(真实截图、确定性图表或简短提示词生成),逐字核对图片中的文字,并为文章接入完整的 SEO 标签。
步骤 0:查找图片存放位置
Which framework is the repo?
├── `next.config.*`, `astro.config.*`, `vite.config.*`, Remix -> files in `public/blog/<slug>/`, src `/blog/<slug>/<name>.webp`
├── Hugo (`hugo.toml` / `config.toml`) -> page bundle next to `index.md`, or `static/blog/<slug>/`
├── Jekyll (`_config.yml`) -> `assets/blog/<slug>/`
└── Other -> match the path pattern of an existing image in a published post
如果仓库中已有图片目录,切勿另建目录;图片位置混杂会破坏大小审计和 CDN 缓存规则。
步骤 1:为每个位置选择视觉类型
为封面以及每个需要配图的 <h2> 按以下流程操作:
What is this slot for?
├── Software product review or UI tutorial (`### 1. ToolName`)?
│ └── Path B: real Playwright screenshot or short `<video>` loop.
│ Never ask an image model to draw a real product's UI; readers spot fake dashboards and it misrepresents the product.
├── Benchmark, dataset, or any chart with numbers?
│ └── Path C: deterministic SVG chart from the real numbers.
│ Never ask an image model for charts or tables; it invents bar heights and misspells values.
├── Cover / OpenGraph (`og:image`)?
│ └── Path A: 16:9, 12-22 word prompt, 1 focal scene + a 3-6 word headline badge.
└── Inline concept (workflow, pricing contrast, architecture)?
├── Does a 2-4 word contrast label make the point in half a second?
│ └── Path A: topic-literal scene + 2-4 quoted labels (`"MANUAL CSV"` vs `"LIVE WEBHOOK"`). No invented numbers.
└── Otherwise -> Path A: zero-text close-up, 12-20 words, tight framing.
步骤 2:提示词规则(仅适用于路径 A)
- 12-22 个词。 长提示词会让图像模型在画面中塞入子面板和难以辨认的小字;短提示词则能生成一个醒目的主体。
- 不要使用深色图标框模板。 切勿提示生成
"cards with glowing wireframe icons"、"holographic brain"、"dark monochrome desk with ice-blue highlights"或奖杯隐喻。请描述明亮、高对比度光线下的实体物品。 - 封面标题徽章:3-6 个带引号的词, 使用目标查询词以及年份或格式徽章,并将其放在画面中央 80% 区域内,避免 X 和 LinkedIn 上的 1.91:1 OG 裁剪将其切掉。
querybench.dev:"Crisp daylight 3D studio, cobalt Postgres server rack splitting into instant branches, bold headline 'POSTGRES CI BRANCHING', badge '2026 BENCHMARK'."deployfast.dev:"High-contrast isometric shipping container launching from a GitHub terminal into orbit, bold text '11 BEST NEXT.JS HOSTS', badge '2026 TESTED'."
- 内嵌对比标签:2-4 个带引号的标签,且只能使用真实数字。 标签中的任何数值都必须出现在文章或
seo/kb/中。invoicekit.io:"Split studio scene: tangled red paper receipts labeled '0.8% BILLING FEE' versus a clean cobalt API card labeled '$19 FLAT', crisp daylight."
步骤 3:生成文件
搭建图片脚本(每个仓库只需一次)
如果 scripts/seo-image.mjs 不存在,就创建它。该脚本同时支持生成图片(路径 A)和压缩现有 PNG(路径 B 和 C),确保所有图片都经过同一套大小处理流程。
// scripts/seo-image.mjs
// Generate: node scripts/seo-image.mjs --prompt "<12-22 words>" --out public/blog/<slug>/<keyword-name>.webp
// Compress: node scripts/seo-image.mjs --input /tmp/raw.png --out public/blog/<slug>/<keyword-name>.webp
// Options: --quality 82 (lower it if the file is over budget), --model <openrouter-image-model>
import fs from "node:fs/promises";
import path from "node:path";
import { parseArgs } from "node:util";
import sharp from "sharp";
const { values: a } = parseArgs({
options: {
prompt: { type: "string" },
input: { type: "string" },
out: { type: "string" },
quality: { type: "string", default: "82" },
model: { type: "string", default: "google/gemini-2.5-flash-image" },
},
});
if (!a.out || (!a.prompt && !a.input)) {
console.error("Usage: --prompt <text> | --input <file>, plus --out <path.webp|.jpg>");
process.exit(1);
}
let raw;
if (a.input) {
raw = await fs.readFile(a.input);
} else {
if (!process.env.OPENROUTER_API_KEY) {
console.error("OPENROUTER_API_KEY is not set");
process.exit(1);
}
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: a.model,
messages: [{ role: "user", content: a.prompt }],
modalities: ["image", "text"],
image_config: { aspect_ratio: "16:9" },
}),
});
const data = await res.json();
const url = data?.choices?.[0]?.message?.images?.[0]?.image_url?.url;
if (!res.ok || !url) {
console.error(`Generation failed (${res.status}): ${JSON.stringify(data).slice(0, 300)}`);
process.exit(1);
}
raw = Buffer.from(url.replace(/^data:image\/\w+;base64,/, ""), "base64");
}
await fs.mkdir(path.dirname(a.out), { recursive: true });
const q = Number(a.quality);
const img = sharp(raw).resize({ width: 1536, withoutEnlargement: true });
const buf = /\.jpe?g$/i.test(a.out)
? await img.jpeg({ quality: q, mozjpeg: true }).toBuffer()
: await img.webp({ quality: q }).toBuffer();
await fs.writeFile(a.out, buf);
const { width, height } = await sharp(buf).metadata();
console.log(`${a.out} ${width}x${height} ${Math.round(buf.length / 1024)} KB`);
使用仓库的包管理器安装 sharp:pnpm add -D sharp(pnpm-lock.yaml)、bun add -d sharp(bun.lock)、yarn add -D sharp(yarn.lock),否则使用 npm i -D sharp。如果仓库没有 package.json(Hugo、Jekyll),请将脚本放在 /tmp/seo-image/ 中,在那里运行 npm init -y && npm i sharp,并传入绝对路径作为 --out,这样仓库就不会新增它原本未使用的 Node 工具。
如果缺少 OPENROUTER_API_KEY,请使用 Agent 内置的图片工具生成图片,将 PNG 保存到 /tmp/,然后以 --input 模式运行脚本。
路径 A:生成图片
node scripts/seo-image.mjs --prompt "<prompt from Step 2>" --out public/blog/<slug>/<keyword-name>.webp
如果网站的 OG 流程不接受 WebP,封面请使用 .jpg;其他位置一律使用 .webp。
路径 B:真实截图
npx playwright install chromium # first run only
npx playwright screenshot --viewport-size "1440, 900" --wait-for-timeout 1500 \
"https://querybench.dev" /tmp/raw-shot.png
node scripts/seo-image.mjs --input /tmp/raw-shot.png --out public/blog/postgres-tools/querybench-explain-analyze-ui.webp
路径 C:确定性图表
对于文章或 seo/kb/proof/benchmarks/*.md 中的数字,请编写一个独立的 1536x864 SVG:根据数值计算条形长度(最长的水平条限制为 760px,确保右侧数值标签不会超出 1536px 的边缘),将每个数值都打印为标签,使用 font-family="system-ui, -apple-system, sans-serif",并采用浅色背景搭配深色文字。将其保存为 /tmp/<name>.svg,然后使用 --input /tmp/<name>.svg 转换。这样图表上的每个数字都能与来源完全一致。
第 4 步:逐一查看每张图片
保存后,立即使用 Agent 的文件查看工具打开每个生成的文件。图像模型经常会拼错字("BENCHMMARK")、重复字母,或把边缘处的文字裁掉。
- 逐字检查每个引用的词语,并确认文字完整地位于画面内。
- 如果文字乱码、模糊、重复或被裁切,或图标布局位于深色方框中,请删除文件并使用包含 12-16 个词语的提示词重新生成。失败 2 次后,将该位置改为零文字模式。
第 5 步:接入图片并完善 SEO
- 使用包含关键词的 kebab-case 文件名,明确描述主题(
postgres-zfs-branch-spinup-benchmark.webp)。切勿使用image1.png、hero.webp、screenshot-2026.png或 UUID,因为文件名是图片搜索会读取的少数文本信号之一。 alt文本长度为 80-160 个字符,用文章中的术语说明图片展示的内容。切勿以"Image of"、"Picture of"或"An infographic showing"开头;屏幕阅读器已经会提示这是图片。- 不佳:
"An infographic of database branching speed"(43 个字符,过于笼统)。 - 推荐:
"Comparison of 50GB Postgres CI branch creation times showing ZFS copy-on-write cloning in 1.8 seconds versus 14 minutes for pg_restore"(134 个字符)。
- 不佳:
- 明确设置
width和height,其值与脚本输出的图片原始像素尺寸相同。每个<img>和<Image>都要设置,以便浏览器预留空间,避免布局偏移。 - 加载优先级:
- 仅封面/首屏 Hero 图片使用
priority(Next.js),或fetchpriority="high"+loading="eager",因为它是 LCP 元素。 - 所有首屏以下的图片使用
loading="lazy"和decoding="async"。正文图片切勿使用priority;它会与 Hero 图片争抢带宽。
- 仅封面/首屏 Hero 图片使用
- 大小限制:
- 封面:宽度至少为 1200px,大小不超过 350 KB。还要在 frontmatter 中设置图片(
image/cover/openGraph.images),以便 X、LinkedIn、Slack 和 Google Discover 显示大卡片。 - 正文图片:不超过 220 KB,格式为
.webp。超出限制时,使用--quality 72重新处理,然后再试--quality 62。
- 封面:宽度至少为 1200px,大小不超过 350 KB。还要在 frontmatter 中设置图片(
- **位置:**将正文图片放在其
<h2>小节的第一段之后,切勿放在标题与首句之间。精选摘要提取和读者都希望标题下方直接呈现文字答案。
## How much faster is ZFS copy-on-write branching than pg_restore in CI?
Cloning a 50.4 GB Postgres 16 database with `pg_restore -j 4` takes a median of 14 minutes and 22 seconds in GitHub Actions. A ZFS copy-on-write snapshot boots the same 180-million-row dataset in 1.8 seconds.
<Image
src="/blog/postgres-ci-testing/postgres-zfs-branch-spinup-benchmark.webp"
alt="Benchmark comparison showing QueryBench ZFS copy-on-write booting a 50GB Postgres branch in 1.8 seconds compared to 14 minutes for pg_restore"
width={1536}
height={864}
loading="lazy"
decoding="async"
/>
第 6 步:完成前进行审核
dir=public/blog/<slug>; post=content/blog/<slug>.mdx
for f in "$dir"/*; do
n=$(basename "$f"); kb=$(( $(wc -c < "$f") / 1024 ))
echo "$kb KB $n"
[ "$kb" -gt 350 ] && echo " FAIL: over 350 KB"
[ "$kb" -gt 220 ] && echo " CHECK: only the cover may exceed 220 KB"
echo "$n" | grep -qiE '^(image|img|hero|screenshot|untitled)[-_.0-9]|^[0-9a-f]{8}-' && echo " FAIL: generic filename"
done
grep -oE 'alt="[^"]*"' "$post" | awk '{ l = length($0) - 6; s = (l < 80 || l > 160) ? "FAIL" : "ok"; print s, l, $0 }'
满足以下条件即表示完成:
- 图片文件位于框架的图片目录中,且文章文件引用了这些图片。
- 每个位置都使用了正确的路径:产品使用屏幕截图,数字使用 SVG 图表,封面和概念图使用简短提示词。
- 已查看每张生成的图片,确认没有拼写错误或文字裁切。
- 审核未输出
FAIL,且唯一超过 220 KB 的文件是封面。 - 封面已添加到 frontmatter/OG 元数据中;正文图片位于其
<h2>小节的第一段之后,并设置了width、height和loading="lazy"。