
Optimise Seo
- 526 installs
- 74 repo stars
- Updated August 5, 2026
- mblode/agent-skills
optimise-seo is an agent skill that improves on-page SEO, metadata, structure, and content patterns so a site or app ranks and converts for developers whose core product pages already exist.
About
optimise-seo is an mblode agent skill for improving on-page SEO across metadata, HTML structure, heading hierarchy, and content patterns. The skill targets ranking and conversion improvements after primary product pages are built, not greenfield app scaffolding. Developers reach for optimise-seo when pages exist but search snippets, titles, descriptions, or internal linking underperform. It audits and recommends concrete SEO fixes rather than writing backend APIs. Use it during launch prep or when refreshing marketing pages for better organic visibility.
- On-page SEO audits
- Metadata and heading structure
- Internal linking strategy
- Technical SEO checks
- Content optimization patterns
Optimise Seo by the numbers
- 526 all-time installs (skills.sh)
- +28 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #747 of 1,879 Marketing & SEO skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mblode/agent-skills --skill optimise-seoAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 526 |
|---|---|
| repo stars | ★ 74 |
| Last updated | August 5, 2026 |
| Repository | mblode/agent-skills ↗ |
How do you improve on-page SEO for product pages?
Improve on-page SEO, metadata, structure, and content patterns so a site or app ranks and converts after core product pages exist.
Who is it for?
Developers with live product or marketing pages who need on-page SEO metadata and structure improvements for ranking and conversion.
Skip if: Greenfield apps with no pages yet, paid ad campaign setup, or backend performance tuning unrelated to search metadata.
When should I use this skill?
The user asks to optimize SEO, fix meta tags, improve page structure, or boost organic ranking for existing pages.
What you get
Optimized meta tags, heading structure, content patterns, and on-page SEO recommendations
- SEO metadata recommendations
- optimized page structure
Files
Optimise SEO
- IS: crawlability, metadata, structured data, canonicals, redirects, hreflang, Core Web Vitals, programmatic SEO, security/privacy headers, and error-page status behaviour for Next.js App Router apps.
- IS NOT: visual redesigns or layout changes (use
ui-design), page-level UI quality review (useui-audit), or making a site readable by AI agents via llms.txt and agent protocols (useagent-ready-audit).
Allowed file surface: metadata, structured data, semantic HTML, internal links, alt text, app/sitemap.ts, app/robots.ts, next.config.ts redirects and headers, error pages, performance tuning. Never touch component styling or layout.
Workflow
Copy and track this checklist:
SEO progress:
- [ ] Step 1: Inventory routes and decide index intent per route
- [ ] Step 2: Fix crawl/index foundations (sitemap, robots, canonicals, redirects, status codes)
- [ ] Step 3: Implement metadata + structured data
- [ ] Step 4: Improve semantics, internal links, and Core Web Vitals
- [ ] Step 5: Validate with references/seo-checklist.md and report evidenceFor steps 2-4, read references/nextjs-implementation.md before writing code; it has the App Router patterns (Metadata API, generateMetadata, sitemap index, JSON-LD component, OG image generation, headers()/redirects() config).
Must-have on every site
app/sitemap.tslisting all public URLs andapp/robots.tslinking to it- Canonical URL set and consistent on every page (one host, one casing, one trailing-slash policy)
- Unique title and description per page via
metadataorgenerateMetadata - OpenGraph + Twitter Card tags with a 1200x630 image
- JSON-LD: Organization and WebSite on the homepage, BreadcrumbList on inner pages, plus Article/Product/FAQ where the content type matches
- One h1 per page with logical h2-h6 hierarchy
- Descriptive alt text, internal links between related pages, CWV within targets (LCP < 2.5s, INP < 200ms, CLS < 0.1)
Programmatic SEO (pages at scale)
- Validate search demand for the repeatable pattern before generating pages
- Each page needs unique value backed by defensible data; templated text swaps are doorway pages
- Clean subfolder URLs, hub-and-spoke linking, breadcrumbs on every page
- Index only strong pages;
noindexthe long tail and monitor indexation and cannibalisation in Search Console
SEO audit (triage order)
1. Crawl/index: robots, sitemap, stray noindex, canonicals, redirect chains, soft 404s 2. Technical: HTTPS, Core Web Vitals, mobile/desktop parity 3. On-page: titles/H1 uniqueness, internal links, remove or noindex thin pages
Redirects and indexing policy
- Permanent moves use 301/308; temporary use 302/307. Never chain redirects; point straight to the final URL.
- A "not found" page must return a real 404, not 200 with a friendly message. Search engines treat 200-for-missing as a soft 404 and refuse to index it.
- Give every route an explicit indexing decision: public pages default to
index, follow; staging, admin, thin, or private routes getmetadata.robotsnoindex (HTML) orX-Robots-Tag(non-HTML, whole environments).
Internationalisation (multi-locale sites)
One URL pattern for all locales; reciprocal hreflang with self-reference plus x-default; translate metadata, not just body; never auto-redirect by IP or Accept-Language. Full rules and the generateMetadata pattern: references/internationalisation.md.
Technical hardening (security, privacy, resilience)
Security headers (HSTS, CSP, nosniff, frame-ancestors, Referrer-Policy, Permissions-Policy), SRI, cookie flags, security.txt, privacy policy and opt-in consent, correct 404/500/503 behaviour, web app manifest. Read references/technical-hardening.md when the task touches headers, cookies, consent, or error pages.
Gotchas
- Don't over-generate thin or doorway pages: indexation drops and sitewide quality signals suffer.
- Don't let canonicals conflict across variants (trailing slash, www, uppercase): search engines split ranking signal between duplicates.
- Don't block crawlers unintentionally via
robots.txt,noindex, or auth walls on routes meant to rank; check before shipping, not after traffic drops. - Don't rely on client-only JS rendering for indexable content; ship SSR/SSG HTML.
- Don't change URLs without 301/308 redirects: link equity and crawl budget are lost.
- Don't add JSON-LD that doesn't match visible page content; Google treats it as spam markup and may demote the page.
- Don't return 200 for "not found" or error pages: soft 404s won't index and drag quality scores down.
- Don't ship
hreflangthat isn't reciprocal across every alternate; search engines ignore non-mutual sets entirely. - Don't serve a maintenance window with 200 or 404: return 503 +
Retry-Afterso the site isn't deindexed. - Don't add
Strict-Transport-Securitywithpreload/includeSubDomainsbefore every subdomain is HTTPS; it's effectively irreversible.
References
- references/nextjs-implementation.md: App Router code patterns; read before steps 2-4
- references/internationalisation.md: URL strategy, hreflang, localised metadata; read for multi-locale sites
- references/technical-hardening.md: security headers, privacy/consent, resilience; read for hardening tasks
- references/seo-checklist.md: pass/fail validation checklist; copy during step 5
Related skills
ui-design: visual direction, palettes, typography, landing-page CROui-audit: page-level UI quality (a11y, forms, layout, microcopy)agent-ready-audit: llms.txt, AI crawler policy, agent-readable contentux-audit: runtime i18n behaviour (locale formatting, plurals, RTL)
Validation (step 5, evidence required)
- Copy references/seo-checklist.md and mark every item pass/fail
- Check HTTP response headers for correct status codes and redirect targets (
curl -sI) - Confirm
robots.txtdirectives and thatsitemap.xmllists all indexed routes with valid absolute URLs - Verify canonical, OpenGraph, and Twitter Card tags appear in served HTML source, not just the React tree
- Run Lighthouse; SEO and Performance >= 90
- Validate JSON-LD per URL with Google's Rich Results Test
- Report remaining blockers with exact URLs and owner/action
Internationalisation (SEO layer)
How multilingual/multi-regional sites tell search engines which version to serve. Covers URL strategy, hreflang, and localised metadata. For runtime formatting (dates/numbers/plurals) and the language switcher UI, that's the ux-audit skill's dark-i18n-* rules.
Contents
URL strategy
Pick one pattern for all locales and keep it:
| Pattern | Example | Notes |
|---|---|---|
| Subdirectory | example.com/de/, example.com/fr/ | Simplest; inherits domain authority. Usually the default. |
| Subdomain | de.example.com | More setup; separate signals. |
| ccTLD | example.de | Strongest geo signal; most expensive to run. |
Optionally localise the slugs too (/de/produkte not /de/products).
hreflang
Declare each language/regional alternate with BCP 47 codes (en, en-GB, de, pt-BR). Rules:
- Reciprocal: every alternate must list every other alternate, including itself.
- Include a self-reference.
- Add
x-defaultfor the unmatched-locale fallback. - Place in one location only (HTML
<head>, HTTPLinkheaders, or the XML sitemap, see below), not duplicated.
<link rel="alternate" hreflang="en" href="https://example.com/en/page" />
<link rel="alternate" hreflang="de" href="https://example.com/de/page" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/page" />In the sitemap (good for scale: keeps localisation metadata out of the head):
<url>
<loc>https://example.com/en/page</loc>
<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/page"/>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/page"/>
</url>Localised metadata
Translate everything in the head and structured data, not just the body. A localised body with an English <title> is a half-translation. Translate:
<title>,<meta name="description">- Open Graph
og:title/og:description(andog:locale) - JSON-LD
name/descriptionfields - image
alttext
See nextjs-implementation.md for the generateMetadata + alternates.languages pattern.
Avoid IP-based redirects
Do not auto-redirect visitors to a locale based on IP geolocation or Accept-Language. It traps users in the wrong language, breaks search crawlers (which crawl from one region), and breaks shared links. Instead:
- Serve the requested URL's locale as-is.
- Optionally show a dismissible banner suggesting another locale ("View this page in Deutsch?"), never a hard redirect.
- Let the user choose via the language switcher.
Next.js SEO Implementation
Patterns for Next.js App Router.
Contents
- Metadata (static and dynamic)
- Sitemap and robots
- Redirects, headers, and indexing
- Internationalisation (hreflang)
- Security headers
- Manifest
- Structured data (JSON-LD)
- OG images
- File structure
Metadata
// app/page.tsx or app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Page Title - Brand',
description: 'Description 150-160 chars',
openGraph: {
title: 'Social Title',
description: 'Social description',
images: [{ url: 'https://example.com/og.png', width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
images: ['https://example.com/twitter.png'],
},
alternates: { canonical: 'https://example.com/page' },
}Dynamic metadata:
export async function generateMetadata(
{ params }: { params: Promise<{ slug: string }> }
): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
return {
title: `${post.title} - Blog`,
description: post.excerpt,
alternates: { canonical: `https://example.com/blog/${slug}` },
}
}Sitemap & Robots
// app/sitemap.ts
import type { MetadataRoute } from 'next'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getAllPosts()
return [
{ url: 'https://example.com', lastModified: new Date(), priority: 1 },
...posts.map(p => ({
url: `https://example.com/blog/${p.slug}`,
lastModified: new Date(p.updatedAt),
priority: 0.7,
})),
]
}// app/robots.ts
import type { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: { userAgent: '*', allow: '/', disallow: '/admin/' },
sitemap: 'https://example.com/sitemap.xml',
}
}For sites over 50,000 URLs (or to split by type), return a sitemap index by exporting generateSitemaps() and reading the id; Next.js serves /sitemap/0.xml, /sitemap/1.xml, … under one index. Add image/video entries with the images/videos fields on a sitemap row when media is JS-loaded or CDN-hosted and not reachable by link-following.
Redirects, headers, and indexing
// next.config.ts: permanent (308) vs temporary (307). Avoid redirect chains.
const config = {
async redirects() {
return [
{ source: '/old-path', destination: '/new-path', permanent: true }, // 308
{ source: '/promo', destination: '/sale', permanent: false }, // 307
]
},
}Indexing policy: public pages default to index, follow. Mark staging, admin, thin, or private pages explicitly, via metadata.robots for HTML routes, or X-Robots-Tag for non-HTML (PDFs, APIs) and whole environments.
// Per-page noindex
export const metadata: Metadata = { robots: { index: false, follow: false } }// next.config.ts: X-Robots-Tag for non-HTML / staging
async headers() {
return [{
source: '/:path*',
headers: [{ key: 'X-Robots-Tag', value: 'noindex' }], // staging only
}]
}Internationalisation (hreflang)
export async function generateMetadata(
{ params }: { params: Promise<{ locale: string; slug: string }> }
): Promise<Metadata> {
const { locale, slug } = await params
const post = await getPost(slug, locale)
return {
title: post.title, // translated per locale
description: post.excerpt, // translated per locale
alternates: {
canonical: `https://example.com/${locale}/blog/${slug}`,
languages: {
'en': `https://example.com/en/blog/${slug}`,
'de': `https://example.com/de/blog/${slug}`,
'x-default': `https://example.com/en/blog/${slug}`,
},
},
}
}Security headers
// next.config.ts: applied to every HTML response
async headers() {
return [{
source: '/:path*',
headers: [
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
{ key: 'Content-Security-Policy', value: "default-src 'self'; frame-ancestors 'self'" },
],
}]
}See technical-hardening.md for values, CSP rollout, cookies, and security.txt.
Manifest
// app/manifest.ts
import type { MetadataRoute } from 'next'
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'Brand',
short_name: 'Brand',
start_url: '/',
display: 'standalone',
theme_color: '#1e3a8a',
background_color: '#ffffff',
icons: [{ src: '/icon-512.png', sizes: '512x512', type: 'image/png' }],
}
}Structured Data
// components/JsonLd.tsx
export function JsonLd({ data }: { data: Record<string, unknown> }) {
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
/>
)
}Note: JSON.stringify on schema objects produces safe output (no user-supplied HTML).
// app/layout.tsx: Organization & WebSite
<JsonLd data={{
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'Brand',
url: 'https://example.com',
logo: 'https://example.com/logo.png',
}} />
<JsonLd data={{
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'Brand',
url: 'https://example.com',
}} />// Breadcrumbs
<JsonLd data={{
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: items.map((item, i) => ({
'@type': 'ListItem',
position: i + 1,
name: item.name,
item: item.url,
})),
}} />// Article
<JsonLd data={{
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
image: [post.image],
datePublished: post.publishedAt,
author: { '@type': 'Person', name: post.author },
}} />OG Images
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og'
export const size = { width: 1200, height: 630 }
export default async function Image(
{ params }: { params: Promise<{ slug: string }> }
) {
const { slug } = await params
const post = await getPost(slug)
return new ImageResponse(
<div style={{
width: '100%', height: '100%', display: 'flex',
alignItems: 'center', justifyContent: 'center',
background: 'linear-gradient(to bottom, #1e3a8a, #3b82f6)',
color: 'white', fontSize: 64, fontWeight: 'bold',
}}>
{post.title}
</div>
)
}File Structure
app/
├── layout.tsx # Organization/WebSite schemas
├── sitemap.ts
├── robots.ts
├── manifest.ts
├── icon.svg
├── apple-icon.png
└── blog/[slug]/
├── page.tsx
└── opengraph-image.tsx
components/
└── JsonLd.tsx
next.config.ts # redirects(), headers(): security + X-Robots-Tag
public/.well-known/
└── security.txtSEO Checklist
Copy this checklist and check off items as you complete them.
Crawl & Index
- [ ]
app/sitemap.tslists all public URLs - [ ]
app/robots.tsallows crawlers, links to sitemap - [ ] No unintended
noindexon public pages - [ ] Canonical URL set and consistent on every page
Meta Tags
- [ ] Unique title (50-60 chars) per page
- [ ] Unique description (150-160 chars) per page
- [ ] OpenGraph: type, url, title, description, image (1200x630)
- [ ] Twitter: card, title, description, image
- [ ] Favicons: favicon.ico, icon.svg, apple-touch-icon.png
Structured Data
- [ ] Organization + WebSite schemas on homepage
- [ ] BreadcrumbList on all non-homepage pages
- [ ] Article/Product/FAQ schemas where applicable
- [ ] Passes Google Rich Results Test
Content & Semantics
- [ ] Single h1 per page with logical h2-h6 hierarchy
- [ ] Descriptive alt text on all images
- [ ] Internal links between related pages
Core Web Vitals
- [ ] LCP < 2.5s (hero image uses
priority) - [ ] INP < 200ms
- [ ] CLS < 0.1 (images have width/height)
- [ ] TTFB < 600ms
Redirects & Status
- [ ] Moved URLs return 301/308 (permanent), not 302/307; no redirect chains
- [ ] Missing pages return real 404 (no soft 404s returning 200)
- [ ] Staging/admin/thin pages have explicit
noindex/X-Robots-Tag
Internationalisation (multi-locale only)
- [ ] One URL pattern for all locales (subdir / subdomain / ccTLD)
- [ ]
hreflangreciprocal across all alternates, with self-ref +x-default - [ ] Metadata translated (title, description, OG, JSON-LD, alt), not just body
- [ ] No automatic IP/Accept-Language locale redirects
Security headers
- [ ] HTTPS enforced; HTTP→HTTPS redirect; HSTS set
- [ ]
Content-Security-Policy,X-Content-Type-Options: nosniff,frame-ancestors - [ ]
Referrer-PolicyandPermissions-Policyset - [ ] Third-party scripts use Subresource Integrity; cookies
Secure/HttpOnly/SameSite - [ ]
/.well-known/security.txtpublished
Privacy
- [ ] Privacy policy present and accurate
- [ ] Non-essential cookies gated behind opt-in consent (EU/UK)
- [ ] Global Privacy Control signal honoured
- [ ] Analytics is cookieless/aggregate where possible; data minimised
Resilience
- [ ] Custom 404/500 return correct status codes, no leaked stack traces
- [ ] Maintenance returns 503 +
Retry-After - [ ] Web app manifest present
- [ ] Uptime monitored from outside own infra
Final Validation
- [ ] Lighthouse SEO score >= 90
- [ ] Lighthouse Performance score >= 90
- [ ] Social sharing previews render correctly
- [ ] Structured data validated per URL
Technical Hardening: Security, Privacy, Resilience
The non-content layer a good site ships: transport and header security, privacy/consent obligations, and graceful failure. These complement SEO: a site that leaks data, ignores consent law, or returns 200 for an error page loses trust and rankings. No visual redesigns; these are headers, policies, and error/status behaviour.
Contents
Security headers
Set these on every HTML response (see nextjs-implementation.md for the next.config headers() form). Test the result at securityheaders.com or Mozilla Observatory.
| Header | Recommended value | Why |
|---|---|---|
Strict-Transport-Security | max-age=63072000; includeSubDomains; preload | Forces HTTPS. Effectively irreversible: only add preload/includeSubDomains once every subdomain is HTTPS. |
Content-Security-Policy | start with default-src 'self', allow-list real origins; prefer nonces/hashes over 'unsafe-inline' | Stops most XSS and data exfiltration. Roll out in Content-Security-Policy-Report-Only first. |
X-Content-Type-Options | nosniff | Stops MIME-sniffing a benign file into script/style. |
Content-Security-Policy: frame-ancestors | 'self' (or trusted embedders) | Clickjacking protection. X-Frame-Options: SAMEORIGIN is the legacy fallback. |
Referrer-Policy | strict-origin-when-cross-origin | Limits URL leakage to other origins. |
Permissions-Policy | disable unused features, e.g. camera=(), microphone=(), geolocation=() | Turns off powerful APIs for your pages and embedded iframes. |
Prerequisites, not headers: serve everything over HTTPS with TLS 1.2/1.3 (redirect HTTP→HTTPS, disable SSL/early TLS).
Subresource Integrity (SRI): for any third-party <script>/<link> you don't control, add integrity="sha384-…" + crossorigin="anonymous" so the browser refuses a tampered file.
<script src="https://cdn.example.com/lib.js"
integrity="sha384-…" crossorigin="anonymous"></script>Cookies
Every cookie: Secure, HttpOnly where JS doesn't need it, and an explicit SameSite. Use the __Host- prefix for session cookies (forces Secure + path=/ + no Domain).
Set-Cookie: __Host-session=…; Secure; HttpOnly; SameSite=Lax; Path=/security.txt and DNS
- Publish
/.well-known/security.txtwith aContact:andExpires:so researchers can report vulnerabilities. - Add a DNS CAA record to restrict which CAs may issue certs for the domain. DNSSEC is optional defence-in-depth (needs full registrar + registry support).
# /.well-known/security.txt
Contact: mailto:security@example.com
Expires: 2027-01-01T00:00:00.000ZPrivacy
- Privacy policy: state what personal data is collected, why, legal basis, sharing, retention, and user rights.
- Cookie consent: in the EU/UK, non-essential cookies/storage need freely-given, specific, opt-in consent before they are set. No pre-ticked boxes; reject must be as easy as accept.
- Global Privacy Control (GPC): honour the
Sec-GPC: 1request signal as an opt-out of sale/sharing (legally required in California and Colorado). - Privacy-respecting analytics: prefer aggregate, cookieless, EU-hostable analytics (e.g. Plausible, Fathom, server-side) to avoid consent and data-transfer problems.
- Data minimisation: collect only what a specific purpose needs, keep it only as long as needed, and redact it from logs/URLs where it leaks.
- Audit third-party scripts: any cross-origin script can read cookies and the URL and exfiltrate page data. Justify each one; lock down with CSP + SRI.
Resilience
- Custom 404 / 500: return the correct status code (a "not found" page must be
404, not200; see soft-404 inSKILL.md). Explain the problem in plain language and offer a way forward; never leak stack traces. - Maintenance: return
503with aRetry-Afterheader so crawlers don't deindex; show when the site will return. - Web app manifest: ship
app/manifest.ts(name, icons,start_url,theme_color,display) so the site installs cleanly. - Monitoring: monitor from outside your own infra (synthetic + real-user), and host the status page on a separate provider so it stays up when the site doesn't.
// Maintenance response
return new Response(maintenanceHtml, {
status: 503,
headers: { "Content-Type": "text/html", "Retry-After": "3600" },
});Related skills
How it compares
Pick optimise-seo over social posting skills when the goal is search engine metadata and page structure, not X.com or social distribution.
FAQ
What does optimise-seo improve?
optimise-seo improves on-page SEO including metadata, page structure, heading hierarchy, and content patterns so existing site or app pages rank better and convert after core product pages are live.
When should developers use optimise-seo?
Developers should use optimise-seo during launch prep or page refreshes when product pages exist but titles, descriptions, or structure need SEO tuning for organic visibility.