Optimise SEO
Contract
| Field | Bound contract |
|---|---|
| Trigger | Improve SEO, Next.js SEO, sitemap, robots, meta tags, structured data, canonical URL, hreflang, Core Web Vitals, crawlability, indexability. |
| Authority | Reversible local: writes only named local project source files (App Router pages and layouts, app/sitemap.ts, app/robots.ts, app/manifest.ts, next.config.ts, JSON-LD components, public/ assets); rollback is version control or restoring recorded prior content. No remote mutation. Audit-only mode reads a deployed origin without editing files. |
| Side effect | Applies or recommends SEO and technical changes within the named scope. No visual redesigns, component styling, or layout changes; no content rewriting; no security-header configuration; llms.txt and AI-agent readability are out of scope. |
| Done | A pass or fail checklist with per-check command evidence: build, curl header and meta checks, and Lighthouse. Each item is marked pass with evidence or fail with a named blocker. Done does not require green Core Web Vitals when external gates are unreachable; those items are marked blocked. |
Inputs
Required: the repo root of the Next.js App Router app to change (edit mode), or a deployed origin URL (audit-only mode); the canonical production host (apex or www). Optional: the locale list (multi-locale steps are skipped without it); which routes are staging, admin, thin, or private; which findings to apply versus only recommend.
With neither a repo nor an origin URL, stop without running.
Procedure
Bound scope and route indexing policies. Determine the mode: edit (repo root provided) or audit-only (deployed origin only). Enumerate routes and decide index intent for each: public routes default to
index, follow; staging, admin, thin, and private routes get explicitnoindex(metadata.robotsfor HTML routes;X-Robots-Tagheaders for non-HTML or whole staging environments). Record the decision per route; do not touch styling or layout. A thin page isnoindexed and omitted from the sitemap until it carries unique content, then indexed and linked internally. Done when: every route has a recorded index intent, the mode is confirmed, and no styling or layout is touched.Configure crawl foundations. In edit mode, write or update:
app/sitemap.tslisting every public URL as absolute URLs withlastModifiedderived from the freshest content date (never hardcoded); above 50,000 URLs, exportgenerateSitemaps()for a sitemap index.app/robots.tsallowing/, disallowing private paths, and linking the sitemap URL. Canonicals: pick one host, one casing, one trailing-slash policy; setalternates.canonicalon every page; 308-redirect the non-canonical host unless the platform already edge-redirects. Redirects: permanent moves use 301 or 308, temporary use 302 or 307, pointing straight at the final URL, never chained. Status codes: a missing page returns a real 404 (a 200 with a friendly message is a soft 404); maintenance windows return 503 withRetry-After. In audit-only mode, verify these with curl against the deployed origin. Done when: sitemap, robots, canonicals, redirects, and status codes are all configured (edit) or verified (audit-only) per the above rules.Configure metadata and structured data. In edit mode, write or update: unique title (50 to 60 chars) and unique description (150 to 160 chars) per page via static
metadataorgenerateMetadata. OpenGraph and Twitter Card tags on every page with a 1200x630 image. Favicons:favicon.ico,icon.svg,apple-touch-icon.png. JSON-LD through oneJsonLdscript component: define each entity once with a stable@id(for examplehttps://<host>/#organization,/#website,/#person) inside a single@graphemitted from the root layout or homepage; per-page schema references entities by@idinstead of duplicating them inline. Homepage:Organization(withlogo) andWebSite. Inner pages:BreadcrumbList. Where the content type matches:ArticleorBlogPosting(author is thePerson@id, publisher is theOrganization@id),Product,FAQPage;ProfilePageon identity pages. Fill recommended fields, not only required ones. In audit-only mode, verify with curl. Done when: unique titles and descriptions, OpenGraph and Twitter Card tags with favicons, and JSON-LD with stable@identities are all in place (edit) or verified (audit-only).Enforce semantics, internal linking, and CWV budgets. In edit mode: one h1 per page with a logical h2 to h6 hierarchy; descriptive alt text on all images; internal links between related pages; every indexable page reachable from navigation, footer, or the sitemap (orphans get a crawl path or are
noindexed and dropped from the sitemap). CWV targets: LCP below 2.5s (hero image withpriority), INP below 200ms, CLS below 0.1 (width and height on all media), TTFB below 600ms. Recompress oversizedpublic/images in place, keeping filenames and formats so references stay valid. Do not move indexable content behind a client-only render. For multi-locale sites (when the locale list is provided): pick one URL pattern for all locales (subdirectory by default), declarehreflangwith BCP 47 codes reciprocally (every alternate lists every other alternate including itself, plus a self-reference andx-default), translate everything in the head and structured data per locale, and never auto-redirect locale by IP geolocation. In audit-only mode, verify with curl and Lighthouse. Done when: semantics, internal linking, CWV budgets, and hreflang (when applicable) are configured (edit) or verified (audit-only).Execute evidence-based validation. Run each check and mark pass or fail with command output:
Check Command Expected Production build pnpm run build(or the repository build command)exits 0 Response headers curl -sI <url>correct status, redirects, canonical host Served HTML metadata curl -s <url>piped togrep -F -e canonical -e "og:" -e "twitter:" -e "application/ld+json"tags present in the served source Robots curl -s <origin>/robots.txtexpected allow and disallow and sitemap link Sitemap curl -s <origin>/sitemap.xmlindexed routes, absolute URLs, fresh lastmodLighthouse pnpm dlx lighthouse <url> --only-categories=seo,performance --output=jsonSEO and Performance scores, or blockers listed External gates (Rich Results Test, Search Console) may be unreachable from the skill's environment. Mark those items as blocked with the exact reason; do not fabricate or estimate metrics. Done when: every check is marked pass with command evidence, fail with a named blocker, or blocked with the exact reason. Done does not require green Core Web Vitals when the Lighthouse or external gates are unreachable; those items are blocked, not failed.
Failure and recovery
- Build failure after an edit: restore that file's recorded prior content; never leave the production build red. Rollback is restoring the recorded prior content or the VCS state of the touched files.
- Missing deployed origin for audit-only: the origin URL is required for audit-only mode. If it is missing or unreachable, mark the audit-only checks as blocked with the exact reason.
- External search gates unreachable: Rich Results Test and Search Console require browser access or post-deploy verification. Mark these as blocked with the exact reason; do not claim Done while pretending they passed.
- Findings conflict: apply the indexing policy,
noindexplus removal from the sitemap, and record the decision; do not widen into rewriting content or redesigning pages. - A live hreflang set is not reciprocal: fix reciprocity before shipping; search engines ignore non-mutual sets, so a partial set must not ship.
- Blockers remain: the terminal result is a blocker list with exact URLs and owner or action per item; Done is never declared with failing or missing evidence.
Output
A completed pass or fail checklist with per-check command evidence, the list of applied file changes each with recorded prior content for rollback (edit mode), a recommendation-only list for findings not applied, and remaining blockers with exact URLs and owner or action. Done is claimed only when every checklist item is pass, fail with a named blocker, or blocked with an exact reason.