Install
$ agentstack add skill-aycanozarpaci-seo-pro-max-skill-seo-pro-max-skill ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
seo-pro-max
> A cross-IDE SEO skill. Works with Claude Code, Cursor, Windsurf, Cline / Roo Code, GitHub Copilot Chat, Continue.dev, Aider, Zed AI, and any agent that can read a Markdown rules file.
Core Principle
This skill does not decide. The user decides; this skill executes.
- The skill MUST NOT silently pick frameworks, file locations, database tables, or admin-panel placements.
- The skill MUST present options and wait for the user before writing files.
- The skill MUST analyze the existing project before suggesting anything.
If the agent is tempted to "just pick the obvious one," it must stop and ask instead.
Operating Protocol (Mandatory Order)
Phase 0 — Read the project
Before any question, the agent must inventory:
- Framework / runtime — look for
package.json,composer.json,requirements.txt,pyproject.toml,Gemfile,go.mod,pubspec.yaml,next.config.*,nuxt.config.*,astro.config.*,vite.config.*,remix.config.*,svelte.config.*,artisan,manage.py. - Rendering model — SSR / SSG / SPA / hybrid / RSC / ISR.
- Routing convention — file-based (
app/,pages/,src/routes/,src/pages/) vs. controller-based (Laravelroutes/web.php, Djangourls.py, Express routers). - Existing SEO surface — any current `
management,metadataexports,Helmet,next/head,useSeoMeta, blade@section('meta')`, etc. - Admin panel — presence of a backoffice (
app/(admin),admin/,/dashboard, Filament, Nova, Django admin, Strapi, custom). - Database — ORM (Prisma, Drizzle, Sequelize, TypeORM, Eloquent, Django ORM), migrations folder, existing settings/options table.
- i18n —
next-intl,next-i18next,vue-i18n,nuxt-i18n, Laravel locales, Django i18n. - Hosting hints —
vercel.json,netlify.toml,wrangler.toml,Dockerfile, nginx configs.
The agent reports findings as a short summary, then proceeds to Phase 0.5.
Phase 0.5 — Dynamic content audit (deterministic, no LLM guesswork)
Static ` / ` on a route that renders dynamic content is the single most common SEO failure mode this skill must catch. Phase 0 finds the admin panel; Phase 0.5 maps which content models actually need per-row metadata.
For every content model discovered (Prisma/Drizzle model, Eloquent model, Django models.Model, TypeORM entity, Strapi content-type, Sanity schema, MDX collection, etc.), the agent answers three yes/no questions purely from file/schema reading — no inference:
- DB SEO fields present? — Does the model/table have at least
seo_titleandseo_descriptioncolumns (also accepted:meta_title,metaTitle,seoTitle, or aseo jsonbcolumn)? - Admin UI exposes them? — Does any admin form/page reference those fields (grep the admin directory for the column names)?
- Public route consumes them? — Does the route handler for that model read those fields and pass them to the framework's metadata API (
generateMetadata,useSeoMeta, `, blade@section('meta'), Django template{% block meta %})? Hardcoded{{ post.title }} | Site` without DB-driven title/description fields counts as NO.
The agent prints a matrix:
Model | DB SEO fields | Admin UI | Public route consumes
----------|---------------|----------|----------------------
Post | ✗ | ✗ | ✗ ← FULL GAP
Product | ✓ | ✗ | ✓ ← PARTIAL (admin missing)
Page | ✓ | ✓ | ✓ ← OK
Any row with at least one ✗ is a dynamic SEO gap. The matrix is carried into Phase 1 as the basis for proactive recommendations. The matrix is also saved verbatim into seo.config.md so future runs can re-verify.
If the project has no content models (pure static brochure site, single-page landing, marketing one-pager), the agent records "no dynamic content detected" and Phase 1 proceeds without proactive recommendations for Surfaces 11/12/13.
Phase 1 — Scope intake (ask, don't assume)
Proactive Recommendations (Phase 0.5 → Phase 1 bridge).
Before showing the opt-in checklist, the agent emits warnings based on the Phase 0.5 matrix. For every row with at least one ✗, the agent prints:
⚠ Dynamic SEO gap detected:
DB fields: Admin UI: Public route consumes:
Without fixing this, every page will ship with identical or hardcoded
/. This silently caps your organic traffic.
Strongly recommended surfaces to tick:
- Surface 11 (Admin-panel UI for SEO data) ← if Admin UI = ✗
- Surface 12 (Database schema for SEO data) ← if DB fields = ✗
- Surface 13 (Per-page overrides) ← always, to define precedence
You may still skip. If you skip, the agent will log
"user-rejected dynamic SEO for — reason: "
into seo.config.md so the next maintainer knows it was an explicit choice,
not an oversight.
The agent waits for the user to either tick the recommended surfaces or provide a skip reason — then shows the full opt-in checklist below.
The agent asks the user, one block at a time, which SEO surfaces are in scope. Default to none (except the surfaces auto-recommended above). Each surface is opt-in.
Which SEO surfaces should I set up?
[ ] Meta basics (title, description, keywords, language, viewport, charset, theme-color)
[ ] Indexing controls (robots meta, X-Robots-Tag, canonical)
[ ] robots.txt
[ ] XML sitemap (single / index / per-locale)
[ ] Open Graph (Facebook, LinkedIn, WhatsApp, Slack, Discord previews)
[ ] Twitter / X Cards
[ ] Schema.org JSON-LD (Organization, WebSite, Article, Product, BreadcrumbList, FAQ, LocalBusiness, ...)
[ ] Favicons & PWA manifest (favicon.ico, apple-touch-icon, manifest.webmanifest, theme-color)
[ ] Search engine verification (Google Search Console, Bing Webmaster, Yandex)
[ ] llms.txt (see note below)
[ ] Admin-panel UI for SEO data
[ ] Database schema for SEO data
[ ] Per-page overrides (slug-level metadata)
[ ] Language, i18n & hreflang (BCP 47, RTL, bidirectional hreflang, x-default)
[ ] Accessibility / WCAG 2.2 AA (image alt-text enforcement, contrast, keyboard, landmarks, heading hierarchy)
[ ] OG image generation (static / dynamic)
[ ] Core Web Vitals & page performance (LCP, CLS, INP, font, third-party scripts)
[ ] Image optimization (srcset, sizes, AVIF/WebP, lazy, CLS prevention)
[ ] URL structure & slug rules (case, separator, Unicode/Turkish policy, length)
[ ] Internal linking & orphan detection (anchor text, link depth, faceted nav)
[ ] Security & Lighthouse headers (HSTS, CSP, Referrer-Policy, Permissions-Policy)
[ ] Search engine submission & IndexNow (GSC, Bing, Yandex, Naver, Baidu)
[ ] SPA hydration & client-side meta updates
Phase 2 — Per-surface drill-down
For every selected surface, the agent asks the specific questions in the matching section below. The agent does not pick defaults — if the user says "you choose," the agent presents 2–3 options with trade-offs and waits.
Phase 3 — Plan & confirm
The agent prints a written plan: files to create, files to modify, migrations to add, routes to register, admin pages to add. No writes until the user types "go" / "ok" / explicit confirmation.
Phase 4 — Implement
Execute the confirmed plan. One commit-sized chunk at a time. After each chunk, report path + 1-line description.
Phase 5 — Verify
- Lint / typecheck pass.
- Build pass.
curl -Ithe homepage and one inner route; show response headers.- Fetch HTML, grep for required tags, report counts.
- HTTP status verification (Surface 2 policy):
curl -sI→ expect200.curl -sI→ expect200.curl -sI '/__definitely-does-not-exist-'→ expect404, NOT200. PrintHTTP/1.1 200here as a FAIL with the soft-404 explanation.curl -sI '/sitemap.xml'→ expect200andContent-Type: application/xml.curl -sI '/robots.txt'→ expect200andContent-Type: text/plain.curl -sIL ''→ expect first hop301to HTTPS, second hop200.curl -sIL '/About'(mixed case) → expect301to/about, then200.curl -sIL '/about/'(trailing slash variant) → expect301to the canonical form, then200.- For any route the user marked as "retired" → expect
410(or301to its replacement). - For any auth-walled route hit without credentials → expect
401or302to login, never200with a login form rendered to bots. - For sitemaps and
robots.txt: hit the public URL and show the body. - For JSON-LD: validate JSON parses; warn if Google's required fields are missing.
- For accessibility (Surface 15): run
axe-coreagainst the homepage and one inner route; print a count of violations grouped by impact (critical / serious / moderate / minor). Run the alt-text auditor: grep all `tags in the built HTML and report any withoutalt=or with auto-generated placeholder alt. Run the heading auditor: per route, printh1` count and the first skipped-level jump (if any).
Surface 1 — Meta Basics
What it covers
`, , (see policy below), , , , , , `.
`` policy (must tell the user verbatim before emitting)
> The behaviour of search engines toward the keywords meta tag is not uniform — and that matters when the site targets non-Western markets. > > - Google — does not use it for ranking. Stated publicly since 2009. Source: https://developers.google.com/search/blog/2009/09/google-does-not-use-keywords-meta-tag > - Bing — does not use it for ranking. Has stated it can be a quality signal against the page when stuffed. > - DuckDuckGo / Brave Search — inherit Bing's signal in practice; do not use it. > - Yandex — historically uses it as a weak hint; current public guidance is unclear, but the field is still parsed. > - Baidu — still reads it as a signal in China. > - Naver (KR) — does not use it. > > Decision rule the skill applies: > > - Site's target market is Western (US/EU/TR/LATAM) only → omit keywords. Adding it is dead weight and clutters the head. > - Site targets RU/CIS (Yandex) or CN (Baidu) → emit keywords with 3–6 honest, comma-separated terms per page; never repeat the same set site-wide; never stuff. > - The skill never fabricates keywords from the slug or body. If the editor cannot provide them, omit the tag.
Questions to ask
- Site name? Tagline?
- Title strategy —
Page | Site/Site — Page/ page only / custom template? - Title max length policy? (Recommend 50–60 chars but ask.)
- Description length policy? (Recommend 140–160 but ask.)
- Default language code (
lang="?")? - Theme color (light / dark variants)?
- Where do per-page overrides live — at the route file, in a CMS, in the database, or all three?
- **`
↔relationship.** The pageshould mirror or paraphrase the(same topic, different phrasing allowed) — they are read together as the page's identity signal by Google. Confirm both are sourced from the same DB field (cheap; recommended) or kept as separate fields (more editorial control; more drift risk). The heading hierarchy rules live in Surface 15 — do not bypass them just because Surface 1 owns the`. keywordsmeta decision — per the policy above, confirm: target markets, emit or omit, and if emit, source of the per-page keyword list.
Output locations (framework-dependent)
- Next.js App Router →
export const metadata/generateMetadatainlayout.tsxand perpage.tsx. - Next.js Pages Router →
next/headornext-seo. - Nuxt 3 →
useSeoMeta()/definePageMeta()/app.headinnuxt.config. - Astro → `` slot in layout + per-page frontmatter.
- SvelteKit → `
in+layout.svelteand+page.svelte`. - Remix →
metaexport per route. - Laravel + Blade →
@stack('meta')and a `` component. - Django →
{% block meta %}inbase.html. - WordPress →
wp_headaction hook in theme. - Plain HTML → `` of each template.
Surface 2 — Indexing Controls
What it covers
`, X-Robots-Tag HTTP header, , alternate hreflang, pagination (rel="prev/next"` is deprecated by Google but ask).
Questions
- Which routes are
index, followvs.noindex/nofollow? - Should auth-walled pages, drafts, search-result pages, and faceted URLs be
noindex? - Canonical strategy — self-canonical on every page, or canonical to a parent for paginated/faceted?
- Are there cross-domain canonicals (mirror sites, syndicated content)?
- Strip query strings from canonical? Which ones (
utm_*,ref,fbclid)? - hreflang pairs — confirm the locale list and the URL pattern (
/en/,?lang=en,en.example.com). - HTTP status policy — confirm the response codes the framework will emit. See "HTTP status code policy" below; non-negotiable rules apply.
HTTP status code policy (mandatory; verified in Phase 5)
Wrong status codes are an SEO-critical defect. Google indexes by status; a 200 OK page that visually says "not found" (a soft 404) gets indexed as a valid page and wastes crawl budget, then later gets demoted with a vague "Soft 404" Search Console warning. The skill enforces real status codes at the framework layer.
Required behavior per route class:
| Route class | Status | Headers / extras | |--------------------------------------------------------------------|----------|-------------------------------------------------------------------------------| | Existing content | 200 | Cache-Control set; Content-Type correct. | | Unknown URL / missing resource | 404 | Real 404, never a 200 with a "not found" UI. Page should still render the branded 404 UI. | | Permanently removed (gone for good, no replacement) | 410 | Use 410 instead of 404 when the URL is intentionally retired and you want Google to drop it faster. | | Permanent move to new URL | 301 | Location: header; one hop only; never chain. | | Temporary move / A-B test / region routing | 302 / 307 | 307 preserves method; 302 may downgrade POST to GET. | | Permanent move that must preserve method (POST → POST) | 308 | Strict permanent redirect. | | Auth-walled page hit unauthenticated | 401 | Not 200 with a login form rendered as the page body for bots. | | Forbidden (logged in but not allowed) | 403 | Same rule: real status. | | Rate-limited | 429 | Retry-After: header. | | Scheduled maintenance / planned downtime | 503 | Retry-After: header. Never use 200 with a maintenance page — Googlebot will index the maintenance message. | | Server error (uncaught) | 5xx | Default framework behavior; verify it's actually 500-class, not 200. | | Empty-state inside an existing valid route (e.g. empty search) | 200 | This is NOT a 404 — the route exists, the result set is empty. Render a clear empty-state UI; noindex is acceptable.|
Anti-patterns the skill refuses:
- Returning
200 OKfrom a customnot-found.tsx/404.vue/404.blade.php/ custom 404 view when the framework would have emitted 404. Confirm the status is preserved by the framework wrapper (Next App RouternotFound(), Nuxt `throw createErro
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: aycanozarpaci
- Source: aycanozarpaci/seo-pro-max-skill
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.