On-Page SEO skill for Codex
Make one page the best answer for one query. Codex starts from the search results and the page's own Search Console queries, not a keyword density checklist. It confirms the page type matches intent, lists concrete gaps against the top results, rewrites the title, description, intro and heading outline, adds contextual internal links, and records a baseline so the change can be judged after recrawl.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- on-page-seo Installs to ~/.agents/skills/on-page-seo/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install on-page-seo The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project on-page-seo Installs to .agents/skills/on-page-seo/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/on-page-seo.zipunzip -o on-page-seo.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) on-page-seo PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $on-page-seo, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Optimizes a single page for a target query — search intent match, title tag, meta description, H1 and heading outline, intro, entity and subtopic coverage, images and alt text, internal links, and freshness — by comparing the page against what currently ranks. Use when asked to optimize a page for a keyword, rewrite titles and meta descriptions, a page ranks on page two, CTR is low for a ranking query, or before publishing an important page.
Use it when
- A page ranks on page two for a query it should win
- CTR is low for a query where the page already ranks
- An important page is about to be published or refreshed
- Titles and meta descriptions need rewriting at the page level
Use something else when
- You need to choose which keywords to target in the first place: use keyword-research
- You are writing a new article from scratch: use seo-content-writing
- Pages are not indexed at all: use technical-seo-audit
How to check the result
Raw HTML shows the new title, H1 and description, URL Inspection renders the new content, and position and CTR for the target query set are compared 28 days after recrawl.
The mistake it prevents
Merging several search intents onto one page to cover more keywords, which dilutes relevance and cannibalizes pages that already rank.
Done well: the page is the best answer on the web for one primary intent, the searcher can tell that from the title link and first screen, and every change is traceable to a gap against the current results or the page’s own Search Console data. Keyword density is not a goal and never appears in the report.
Inputs to gather first
- Target query and the page URL (or file in the repo). If the user gives several
keywords, group them: one page serves one intent. Clustering belongs to
keyword-research. - Current performance for the page: queries, impressions, position, CTR from Search
Console (ask for an export if Codex cannot reach it; see
google-search-console). - The current top results for the query. Looking at live SERPs needs network access and results vary by location; if unavailable, ask the user for the top 5 URLs or a screenshot and say the analysis depends on it.
- In a repo, read
AGENTS.mdand find where titles/meta are set (CMS fields, frontmatter,generateMetadata, a<Seo>component) before editing.
Workflow
- [ ] 1. Classify intent and format from the SERP
- [ ] 2. Diagnose: why this page is not the best result
- [ ] 3. Fix the content gap and structure
- [ ] 4. Write the title link and meta description
- [ ] 5. Headings, media, links
- [ ] 6. Technical sanity for this URL
- [ ] 7. Record baseline and measurement window
1. Intent and format
| SERP shows mostly | Intent | Page must be |
|---|---|---|
| Guides, “how to”, definitions | Informational | A direct answer early, then depth |
| Listicles, “best X”, comparisons | Commercial investigation | Criteria, honest comparison, evidence of use |
| Product/category pages | Transactional | Product facts, price, availability, reviews |
| Brand homepage, login | Navigational | Usually not winnable unless you are the brand |
| Map pack, “near me” | Local | Location page, see local-seo |
If the page type mismatches the SERP (a product page targeting a “how to” query), stop: recommend a different page or a new one instead of optimizing the wrong format.
2. Diagnose
Compare the page with the top 3–5 results and note concretely:
- Does the page answer the core question in the first 100–150 words?
- Which subtopics, steps, data, examples or comparisons do top pages cover that this one lacks? Which does this page cover that they do not (information gain to keep)?
- Evidence of experience: original screenshots, test results, numbers, named author with relevant background. This is where pages win under Google’s helpful content guidance.
- Is content outdated (versions, prices, years, screenshots)?
- For pages already ranking 4–20, the Search Console queries list shows the subtopics Google already associates with the page; cover the high-impression ones explicitly.
3. Content and structure
- Lead with the answer, then expand. Cut preamble (“In today’s world…”).
- Add missing subtopics as their own sections when they serve the same intent; split into a separate page when they are a different intent.
- Use the vocabulary searchers use, naturally. Synonyms and related entities matter more than repeating the exact phrase; never stuff.
- Prefer tables for comparisons and specs, numbered lists for procedures.
- Do not inflate length to match competitors. Length follows what the answer needs.
4. Title link and meta description
Title tag rules:
- Lead with the primary topic phrase, add the differentiator (year only if content is genuinely updated, audience, format, number), brand last.
- Keep it under about 60 characters (roughly 600 px on desktop) so it is not truncated.
- Unique per page. Google rewrites titles that are stuffed, boilerplate-heavy or that mismatch the H1; matching H1 and title intent reduces rewrites.
Meta description rules:
- 120–160 characters, a specific summary with the benefit and a concrete detail. Not a ranking factor; it influences CTR when Google uses it (it often generates its own snippet from page text, so the intro matters too).
Write 3 options and pick one with a one-line reason:
Title A: Postgres Connection Pooling: PgBouncer vs Built-in Options (2026)
Title B: How to Set Up PgBouncer for Postgres Connection Pooling
Title C: Postgres Connection Pooling Guide | Example
Pick: B. The SERP is dominated by setup tutorials; B states the task and the tool.
Meta: Configure PgBouncer in transaction mode, size pools from max_connections, and
avoid the prepared-statement trap. Tested configs for Postgres 17.
5. Headings, media, links
- One H1 that states the page topic (multiple H1s are not penalized but one is clearer). H2s map to the subtopics from step 2; a reader scanning only H2s should understand the page. Do not skip levels for styling.
- Images: descriptive file names,
altthat describes the image in context (emptyalt=""for decorative images), explicitwidth/height, modern format, lazy-load below the fold only. The LCP image must not be lazy-loaded. - Internal links: add 2–5 contextual links to this page from relevant, strong pages,
with descriptive anchors; link out from this page to the next step in the journey.
Site-wide architecture belongs to
internal-linking. - External links to primary sources (docs, studies, standards) where claims need them.
6. Technical sanity for this URL
- Returns 200, self-canonical, indexable, in the sitemap, not blocked by robots.txt.
- Rendered content matches raw HTML for the main text.
- URL is short, readable, lowercase, hyphenated. Do not change an established URL just for a keyword; if you must, 301 the old one and update internal links.
- Structured data appropriate to the type (see
schema-markup).
7. Baseline and measurement
Record before shipping: clicks, impressions, average position, CTR for the page and its top 10 queries over the last 28 days. Re-measure 28 days after Google recrawls the page (check last crawl in URL Inspection). Compare like-for-like periods and note seasonality.
Gotchas
- “Optimize for 15 keywords” usually means two or three intents. Forcing them onto one page dilutes all of them and can cannibalize another page already ranking.
- Changing the title of a page that already ranks top 3 is risky; change titles to fix CTR only when CTR is clearly below peers at the same position.
- Updating the date without updating the content is a known spam signal and misleads users.
- AI-generated filler expansions hurt: Google’s scaled content abuse policy targets content produced at scale to manipulate rankings regardless of how it is made.
- Hidden text, tabs and accordions are fine if the content is available in the HTML; text injected only after interaction may not be indexed.
Verification
- Raw HTML (
curl -s URL | grep -oE '<title>[^<]*|<h1[^>]*>[^<]*|<meta name="description"[^>]*>') shows the new title, H1 and description. - URL Inspection → Test live URL renders the new content; request indexing once.
- After the measurement window, report changes in position and CTR for the target query set, not just the single head term.
Output template
# On-page review: /guides/pgbouncer-setup → "pgbouncer setup"
**Intent:** informational how-to (8 of top 10 are tutorials). Page type matches.
**Baseline (28d):** 3,120 impr, 41 clicks, pos 11.4, CTR 1.3%.
**Gaps vs top 3:** no pool_mode explanation, no config file example, no sizing formula,
screenshots from 2022.
**Changes:**
1. Title → "How to Set Up PgBouncer for Postgres Connection Pooling"
2. New H2s: Choosing pool_mode; Sizing pools; Prepared statements in transaction mode
3. Intro rewritten to answer in 2 sentences; outdated screenshots replaced
4. 3 internal links added from /postgres/performance, /blog/connection-limits, /docs/deploy
**Measure:** 28 days after recrawl; target queries listed in appendix. What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "On-Page SEO"
short_description: "Make one page the best answer for one query"
default_prompt: "$on-page-seo Optimize our PgBouncer guide for 'pgbouncer setup', it is stuck at position 11" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.