enhance-web-seo — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited enhance-web-seo (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Degree of freedom: MIXED. Title/snippet judgment [HIGH freedom]; live probes, robots/sitemap, and re-measure [LOW freedom — run exactly].
Google does not rank pages it cannot read, understand, or trust. This skill finds every gap between your app and how search engines see it, then fixes them in order of impact.
Before browser work, read protocol-browser-anti-stall and apply it.
| Skill | Owns |
|---|---|
| enhance-web-seo (this) | SEO audit and fixes: meta, structured data, sitemap, canonical, indexing |
workflow-launch-ready | Launch-preparation sweep for a new app or major release |
workflow-gtm | Taking a shipped repo to market: measure, message, get found, launch |
Detect: Next.js App Router; rootmetadataonly; nositemap.ts;/robots.txt404. Measure: product pages inherit the home title; no canonical; no JSON-LD; LCP 3.1s. Fix:generateMetadataper product;app/sitemap.ts+ robots; Product JSON-LD; image dimensions. Re-measure: unique titles; OG image resolves; sitemap lists products; LCP < 2.5s.
Disallow: / or noindexaudit-bundle-size; AEO citations → plan-aeo-readinesspackage.json → Next.js Metadata API, remix-utils/seo, @vueuse/head, etc.
src/app/layout.tsx → existing metadata export (Next.js App Router)
src/app/head.tsx → legacy Head component
public/robots.txt → existing robots config
public/sitemap.xml → existing sitemapAlso check if a sitemap generator is configured (next-sitemap, astro-sitemap, SvelteKit's @sveltejs/adapter-static sitemap, etc.).
For every public-facing route, run the following:
// eval after goto
const seo = await page.evaluate(() => ({
title: document.title,
description: document.querySelector('meta[name="description"]')?.content,
canonical: document.querySelector('link[rel="canonical"]')?.href,
og_title: document.querySelector('meta[property="og:title"]')?.content,
og_description: document.querySelector('meta[property="og:description"]')?.content,
og_image: document.querySelector('meta[property="og:image"]')?.content,
og_url: document.querySelector('meta[property="og:url"]')?.content,
twitter_card: document.querySelector('meta[name="twitter:card"]')?.content,
robots: document.querySelector('meta[name="robots"]')?.content,
jsonld: [...document.querySelectorAll('script[type="application/ld+json"]')]
.map(s => s.textContent),
}));const headings = await page.evaluate(() =>
[...document.querySelectorAll('h1,h2,h3,h4,h5,h6')]
.map(h => ({ tag: h.tagName, text: h.textContent.trim().slice(0, 60) }))
);Rules: exactly one <h1> per page; <h2> never skips to <h4> without <h3>.
const images = await page.evaluate(() =>
[...document.querySelectorAll('img')]
.filter(img => !img.alt || img.alt.trim() === '')
.map(img => img.src.slice(-60))
);CWV (LCP, INP, CLS) is a ranking input. Measure field p75 via CrUX/PSI. Do not fix CWV here — hand off:
audit-performance §Loading Priority & Speculationaudit-bundle-sizeenhance-web-instant-navRecord before/after p75 in this skill's re-measure step.
goto to /robots.txt. Check:
Disallow: / unless intentional (private app)Sitemap: https://yourdomain.com/sitemap.xmlNavigate to /sitemap.xml or /sitemap-index.xml. Check:
/)<lastmod> dates are current, not hardcoded to a past dateValidate each JSON-LD block found in Phase 1a:
@context: "https://schema.org" present@type matches the page content (Article, Product, Organization, WebSite, BreadcrumbList, FAQPage, etc.)references/structured-data-types.md)<link rel="canonical"> pointing to its definitive URLFollow /research: Context7 for the framework's metadata, sitemap, and robots API; Firecrawl for current Google guidance on CWV, structured data, and rich results, dated to now. Anchor to the framework version actually installed.
Every public page needs a unique, descriptive title (50–60 chars) and meta description (150–160 chars).
Next.js App Router:
// app/page.tsx
export const metadata: Metadata = {
title: 'Specific Page Title — Brand Name',
description: 'One or two sentences that describe this page for someone scanning search results.',
};Next.js dynamic routes:
export async function generateMetadata({ params }): Promise<Metadata> {
const item = await fetchItem(params.id);
return {
title: `${item.name} — Brand Name`,
description: item.summary,
openGraph: { title: item.name, description: item.summary, images: [item.coverImage] },
};
}Required for social sharing previews. Every page needs at minimum:
openGraph: {
title: 'Page Title',
description: 'Page description',
url: 'https://yourdomain.com/page',
siteName: 'Brand Name',
images: [{ url: '/og-image.png', width: 1200, height: 630 }],
type: 'website', // or 'article' for blog posts
},
twitter: {
card: 'summary_large_image',
title: 'Page Title',
description: 'Page description',
images: ['/og-image.png'],
},robots.txt (static file at public/robots.txt):
User-agent: *
Allow: /
Sitemap: https://yourdomain.com/sitemap.xmlSitemap — use the framework's built-in generator where available:
app/sitemap.ts returning MetadataRoute.Sitemap+server.ts returning XML responsesitemap[.]xml.ts resource routeAdd to the relevant page layout:
// For a website home page
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'Brand Name',
url: 'https://yourdomain.com',
potentialAction: {
'@type': 'SearchAction',
target: 'https://yourdomain.com/search?q={search_term_string}',
'query-input': 'required name=search_term_string',
},
};
// Inject via: <script type="application/ld+json">{JSON.stringify(jsonLd)}</script>See audit-bundle-size for JS bundle → LCP fixes. For CLS: ensure images have explicit width/height, fonts use font-display: swap, ads/embeds have reserved space.
After fixes, re-run Phase 1 checks on each modified page. Confirm:
document.title and meta description match the intentPer page:
- [ ] Unique descriptive title (50–60 chars)
- [ ] Meta description (150–160 chars), not cut off
- [ ] Exactly one <h1>
- [ ] Heading hierarchy correct (no skipped levels)
- [ ] All images have alt text
- [ ] Canonical URL present and correct
- [ ] OG title, description, image
- [ ] Twitter card meta
- [ ] JSON-LD structured data where relevant
Site-wide:
- [ ] robots.txt exists and correct
- [ ] Sitemap exists and up to date
- [ ] No important pages noindexed accidentally
- [ ] HTTPS enforced, no mixed content
- [ ] LCP < 2.5 s
- [ ] CLS < 0.1~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.