What is the SEO Blog Images skill?
SEO Blog Images is a images & art skill by Illyism for Claude Code, Cursor, Codex and other AI coding agents. Plan, generate, compress, verify, and wire high-CTR cover images and inline proof visuals into blog posts (MDX, Astro, Next.js, Hugo, HTML) with strict image SEO checks. Use when adding a blog cover or OG image, generating inline visuals without dark icon-box AI slop, capturing real product screenshots or data charts, or auditing image alt text, filenames, and Core Web Vitals attributes.
The default AI blog visual hurts the post: a dark background with 5 glowing wireframe icons, garbled infographic labels, a 2.4 MB PNG named image-1.png, and no width/height, so the layout jumps on load. This workflow picks the right visual type per slot, produces it (real capture, deterministic chart, or a short-prompt generation), reads every letter back, and wires SEO-complete tags into the post.
When to use it
Use when adding a blog cover or OG image, generating inline visuals without dark icon-box AI slop, capturing real product screenshots or data charts, or auditing image alt text, filenames, and Core Web Vitals attributes.
What the SKILL.md covers
- Step 0: Find where images live
- Step 1: Pick the visual type per slot
- Step 2: Prompt rules (Path A only)
- Step 3: Produce the file
- Scaffold the image script (once per repo)
- Path A: generate
- Path B: real screenshot
- Path C: deterministic chart
- Step 4: Read back every image
- Step 5: Wire it in with full image SEO
- Author
- Illyism
- Pack
- Illyism/seo-skills (30 skills)
- Category
- Images & art, #147 of 287
- Alyn Score
- 66 / 99
- Files
- 1
- License
- Not stated
- Last updated
- 4 days ago
- Works with
- Claude Code, Claude, Cursor, Codex, Gemini CLI, Copilot
SEO Blog Images compared to similar skills
| Skill | Alyn Score | GitHub stars | Updated |
|---|---|---|---|
| SEO Blog Images · Illyism | 66 | 1.2k | 4 days ago |
| Huashu Wechat Image · alchaincyf | 64 | 2.3k | 7 days ago |
| Icon Generator · anbeime | 55 | 9.6k | 2 days ago |
| Image · Corey Haines | 89 | 60k | 4 days ago |
Install SEO Blog Images
Pick your agent. Every command is ready to paste.
npx alyn-cli add seo-blog-imagesDetects your agents (Claude Code, Cursor, Codex and 70+ more) and installs to the right folder. Add -g to install globally.
npx skills add Illyism/seo-skills --skill seo-blog-imagesOr use the open skills CLI directly.
01
Install it
Run the command above, or upload the folder in Claude.
02
Ask naturally
Describe the task. Your agent loads seo-blog-images when the request matches its description.
03
Name it if needed
If it does not trigger, say “use the seo-blog-images skill”. In Codex, type $seo-blog-images.
Blog images and image SEO
The default AI blog visual hurts the post: a dark background with 5 glowing wireframe icons, garbled infographic labels, a 2.4 MB PNG named image-1.png, and no width/height, so the layout jumps on load. This workflow picks the right visual type per slot, produces it (real capture, deterministic chart, or a short-prompt generation), reads every letter back, and wires SEO-complete tags into the post.
Step 0: Find where images live
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
Never invent a new image directory when the repo already has one; mixed locations break the size audit and CDN caching rules.
Step 1: Pick the visual type per slot
Walk this for the cover and for each <h2> that needs a visual:
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.
Step 2: Prompt rules (Path A only)
- 12-22 words. Long prompts make image models crowd the frame with sub-panels and tiny unreadable text; short prompts produce one bold subject.
- No dark icon-box templates. Never prompt for
"cards with glowing wireframe icons","holographic brain","dark monochrome desk with ice-blue highlights", or trophy metaphors. Describe tangible objects under bright, high-contrast light. - Cover headline badge: 3-6 quoted words, using the target query plus a year or format badge, kept inside the center 80% of the frame so the 1.91:1 OG crop on X and LinkedIn does not cut it.
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'."
- Inline contrast labels: 2-4 quoted labels, only real numbers. Any figure in a label must exist in the post or
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."
Step 3: Produce the file
Scaffold the image script (once per repo)
If scripts/seo-image.mjs does not exist, create it. It handles both generation (Path A) and compression of any existing PNG (Paths B and C), so every image goes through the same size pipeline.
// 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`);
Install sharp with the repo's package manager: pnpm add -D sharp (pnpm-lock.yaml), bun add -d sharp (bun.lock), yarn add -D sharp (yarn.lock), else npm i -D sharp. If the repo has no package.json (Hugo, Jekyll), put the script in /tmp/seo-image/, run npm init -y && npm i sharp there, and pass absolute --out paths, so the repo gains no Node tooling it does not use.
If OPENROUTER_API_KEY is missing, generate with the agent's built-in image tool, save the PNG to /tmp/, and run the script in --input mode.
Path A: generate
node scripts/seo-image.mjs --prompt "<prompt from Step 2>" --out public/blog/<slug>/<keyword-name>.webp
Use .jpg for the cover when the site's OG pipeline rejects WebP; use .webp everywhere else.
Path B: real screenshot
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
Path C: deterministic chart
For numbers from the post or seo/kb/proof/benchmarks/*.md, write a self-contained 1536x864 SVG: bar lengths computed from the values (cap the longest horizontal bar at 760px so right-hand value labels never clip at the 1536px edge), every value printed as a label, font-family="system-ui, -apple-system, sans-serif", dark text on a light background. Save it as /tmp/<name>.svg, then convert with --input /tmp/<name>.svg. Every number on the chart then matches the source exactly.
Step 4: Read back every image
Open every generated file with the agent's file-viewing tool right after saving. Image models misspell ("BENCHMMARK"), duplicate letters, and clip text at the edge.
- Check every quoted word letter by letter and confirm it sits fully inside the frame.
- Garbled, blurry, duplicated, or clipped text, or a dark icon-box layout: delete the file and regenerate with a 12-16 word prompt. After 2 failed attempts, switch that slot to zero-text mode.
Step 5: Wire it in with full image SEO
- Keyword kebab-case filenames naming the subject (
postgres-zfs-branch-spinup-benchmark.webp). Neverimage1.png,hero.webp,screenshot-2026.png, or UUIDs, because the filename is one of the few text signals image search reads. alttext of 80-160 characters stating what the image shows in the post's terms. Never start with"Image of","Picture of", or"An infographic showing"; screen readers already announce the image.- Bad:
"An infographic of database branching speed"(43 chars, generic). - Good:
"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 chars).
- Bad:
- Explicit
widthandheightequal to the intrinsic pixels the script printed, on every<img>and<Image>, so the browser reserves the space and the layout does not shift. - Loading priority:
- Cover / above-the-fold hero only:
priority(Next.js) orfetchpriority="high"+loading="eager", because it is the LCP element. - Every image below the fold:
loading="lazy"anddecoding="async". Neverpriorityon body images; it competes with the hero for bandwidth.
- Cover / above-the-fold hero only:
- Size budgets:
- Cover: 1200px or wider, 350 KB or less. Also set it in frontmatter (
image/cover/openGraph.images) so X, LinkedIn, Slack, and Google Discover render the large card. - Inline: 220 KB or less,
.webp. Over budget: re-run with--quality 72, then--quality 62.
- Cover: 1200px or wider, 350 KB or less. Also set it in frontmatter (
- Placement: put inline images after the first paragraph of their
<h2>section, never between the heading and its first sentence. Featured snippet extraction and readers both expect the text answer directly under the heading.
## 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"
/>
Step 6: Audit before finishing
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 }'
Done means:
- Image files exist in the framework's image directory and the post file references them.
- Each slot used the right path: screenshot for products, SVG chart for numbers, short prompt for cover and concepts.
- Every generated image was viewed with zero misspelled or clipped text.
- The audit prints no
FAIL, and the only file over 220 KB is the cover. - The cover is in frontmatter/OG metadata; inline images sit after the first paragraph of their
<h2>withwidth,height, andloading="lazy".
Tips for SEO Blog Images
- • Start a fresh session after installing so your agent picks up the new skill.
- • It is part of a pack with 30 skills. Install only the ones you need with --skill to keep triggers precise.
- • If two skills overlap, mention the one you want by name.
Questions about SEO Blog Images
What does the SEO Blog Images skill do?
Plan, generate, compress, verify, and wire high-CTR cover images and inline proof visuals into blog posts (MDX, Astro, Next.js, Hugo, HTML) with strict image SEO checks. Use when adding a blog cover or OG image, generating inline visuals without dark icon-box AI slop, capturing real product screenshots or data charts, or auditing image alt text, filenames, and Core Web Vitals attributes.
How do I install SEO Blog Images?
Run "npx alyn-cli add seo-blog-images" in your project. The Alyn CLI installs it for Claude Code, Cursor, Codex and 70+ other agents. In Claude on the web, upload the skill folder as a ZIP under Customize > Skills.
Does SEO Blog Images work in Cursor and Codex?
Yes. SEO Blog Images uses the open SKILL.md format, so it works in Claude Code, Claude, Cursor, OpenAI Codex, Gemini CLI, GitHub Copilot and other agents that support agent skills.
When should I use the SEO Blog Images skill?
Use when adding a blog cover or OG image, generating inline visuals without dark icon-box AI slop, capturing real product screenshots or data charts, or auditing image alt text, filenames, and Core Web Vitals attributes.
What is inside the SEO Blog Images skill?
The SKILL.md covers Step 0: Find where images live, Step 1: Pick the visual type per slot, Step 2: Prompt rules (Path A only), Step 3: Produce the file, Scaffold the image script (once per repo), Path A: generate.
What is a good alternative to SEO Blog Images?
Huashu Wechat Image by alchaincyf is the highest-ranked similar skill from another creator, with an Alyn Score of 64 compared to 66 for SEO Blog Images.
Is SEO Blog Images free?
SEO Blog Images is open source on GitHub. It is free to install; check the license before commercial use.
Who made SEO Blog Images?
SEO Blog Images is published by Illyism in the Illyism/seo-skills repository, together with 29 other skills.




