mcpField
S
seo-blog-images

seo-blog-images

github.com/illyism营销与增长1.2k159 次安装3 天前更新
代码仓库

关于 seo-blog-images

用于为博客文章制作并集成符合 SEO 要求的图片的工作流技能。它按用途选择截图、确定性图表或生成式视觉内容,并检查图片文件与元数据。

主要功能

  1. 01为每个图片位置选择视觉形式
  2. 02生成或压缩图片文件
  3. 03根据真实数值制作图表
  4. 04检查图片中的文字错误
  5. 05添加 SEO 元数据和尺寸属性

适用场景

  1. 01制作博客文章封面图
  2. 02为教程添加产品截图
  3. 03用图表呈现基准测试数据
  4. 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)

  1. 12-22 个词。 长提示词会让图像模型在画面中塞入子面板和难以辨认的小字;短提示词则能生成一个醒目的主体。
  2. 不要使用深色图标框模板。 切勿提示生成 "cards with glowing wireframe icons"、"holographic brain"、"dark monochrome desk with ice-blue highlights" 或奖杯隐喻。请描述明亮、高对比度光线下的实体物品。
  3. 封面标题徽章: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'."
  4. 内嵌对比标签: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

  1. 使用包含关键词的 kebab-case 文件名,明确描述主题(postgres-zfs-branch-spinup-benchmark.webp)。切勿使用 image1.png、hero.webp、screenshot-2026.png 或 UUID,因为文件名是图片搜索会读取的少数文本信号之一。
  2. 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 个字符)。
  3. 明确设置 width 和 height,其值与脚本输出的图片原始像素尺寸相同。每个 <img> 和 <Image> 都要设置,以便浏览器预留空间,避免布局偏移。
  4. 加载优先级:
    • 仅封面/首屏 Hero 图片使用 priority(Next.js),或 fetchpriority="high" + loading="eager",因为它是 LCP 元素。
    • 所有首屏以下的图片使用 loading="lazy" 和 decoding="async"。正文图片切勿使用 priority;它会与 Hero 图片争抢带宽。
  5. 大小限制:
    • 封面:宽度至少为 1200px,大小不超过 350 KB。还要在 frontmatter 中设置图片(image / cover / openGraph.images),以便 X、LinkedIn、Slack 和 Google Discover 显示大卡片。
    • 正文图片:不超过 220 KB,格式为 .webp。超出限制时,使用 --quality 72 重新处理,然后再试 --quality 62。
  6. **位置:**将正文图片放在其 <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"。

评论

支持 Markdown