L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Design et création · SKILL.md

frontend-seo

frontend-seo

Use this skill when the user says 'seo', 'meta tags', 'open graph', 'structured data', 'JSON-LD', 'sitemap', 'robots.txt', 'canonical url', 'ssr seo'. This skill enforces SEO best practices — meta tag optimization, structured data injection, XML sitemap generation, robots.txt configuration, canonical URL handling, and SSR strategies for search engine crawle…

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

name: frontend-seo description: > Use this skill when the user says 'seo', 'meta tags', 'open graph', 'structured data', 'JSON-LD', 'sitemap', 'robots.txt', 'canonical url', 'ssr seo'. This skill enforces SEO best practices — meta tag optimization, structured data injection, XML sitemap generation, robots.txt configuration, canonical URL handling, and SSR strategies for search engine crawlers. Applies to any frontend stack. version: "2.0.0" author: "j4flmao" license: "MIT" compatibility: claude-code: true cursor: true codex: true windsurf: true tags: [frontend, seo, phase-3, universal]

Frontend SEO

Purpose

Optimize web applications for search engine ranking and rich snippet eligibility through meta tags, structured data, sitemaps, and SSR-friendly rendering. Covers Core Web Vitals, hreflang for multi-language, canonical URLs, JSON-LD schemas, and automated audit tooling.

Agent Protocol

Trigger

Exact phrases: "seo", "meta tags", "open graph", "og tags", "structured data", "json-ld", "schema.org", "sitemap", "robots.txt", "canonical url", "seo audit", "seo optimization", "ssr seo", "rich results", "hreflang", "core web vitals", "lighthouse seo"

Input Context

  • Check for existing <head> meta tag patterns and SSR framework
  • Determine if a sitemap or robots.txt already exists
  • Identify the routing library for canonical URL generation
  • Verify whether structured data is already present
  • Check multi-language setup for hreflang requirements

Output Artifact

No file output unless requested.

Response Format

  1. Output complete HTML <head> snippet for meta tags — never abbreviate
  2. Output full JSON-LD blocks — never truncate with ellipsis
  3. For sitemaps, output full XML structure with at least 3 example <url> entries
  4. No preamble. No postamble. No explanations.

Completion Criteria

  • <title> (50-60 chars) and <meta name="description"> (150-160 chars) on every page
  • Open Graph tags on every page
  • Twitter Card tags present
  • Canonical URL set on every page
  • JSON-LD structured data present for relevant content types
  • sitemap.xml lists all public pages
  • robots.txt allows crawling of public paths
  • SSR or prerendering ensures crawlers see fully rendered content
  • Lighthouse SEO audit scores 90+

Max Response Length

150 lines unless generating full sitemap or multiple JSON-LD blocks.

SEO Architecture / Decision Trees

Meta Tag Strategy Decision Tree

Page type?
  |-- Content page (article, product, blog post) -->
  |     OG: title, description, image derived from content
  |     JSON-LD: Article or Product schema
  |     Title: "Primary Keyword — Secondary Keyword | Site"
  |
  |-- Listing / category page -->
  |     OG: category name + generic category image
  |     JSON-LD: CollectionPage or BreadcrumbList
  |     Title: "Category Name | Site"
  |
  |-- User profile / dashboard (auth required) -->
  |     OG: site-level defaults (noindex to prevent crawling)
  |     Robots: noindex, nofollow
  |
  |-- Error page (404, 500) -->
        OG: site-level defaults with error context
        Title: "Page Not Found | Site"

Rendering Strategy for SEO Decision Tree

SEO critical?
  |-- YES -->
  |     |-- Public content (same for all users)? -->
  |     |     SSG or ISR (pre-rendered, crawler sees full content immediately)
  |     |
  |     |-- User-specific? -->
  |           SSR (crawler gets full HTML, but user data is dynamic)
  |
  |-- NO -->
        |-- Behind auth? --> CSR is fine (Google won't index)
        |-- Not auth, but low SEO value? --> CSR with basic SSG shell

Structured Data Decision Tree

Content type?
  |-- Article / Blog Post -->
  |     Schema: Article, NewsArticle, or BlogPosting
  |     Required: headline, author, datePublished, dateModified, image
  |
  |-- Product -->
  |     Schema: Product
  |     Required: name, image, description, offers (price, availability)
  |     Optional: review, aggregateRating, brand
  |
  |-- Breadcrumb navigation -->
  |     Schema: BreadcrumbList
  |     Required: position, name, item URL for each level
  |
  |-- FAQ page -->
  |     Schema: FAQPage
  |     Required: mainEntity array with Question/Answer pairs
  |
  |-- Organization / Local Business -->
  |     Schema: Organization or LocalBusiness
  |     Required: name, logo, url, address (for LocalBusiness)
  |
  |-- Event -->
        Schema: Event
        Required: name, startDate, location, offers

Workflow

Step 1: Audit Current State

npx lighthouse https://example.com --view --preset=desktop

Check existing <head> for title, description, OG, Twitter cards, canonical.

Step 2: Implement Base Meta Tags

Every page must have:

<title>Primary Keyword — Secondary Keyword | Site Name</title>
<meta name="description" content="Compelling value prop with keyword, 150-160 chars." />
<link rel="canonical" href="https://example.com/current-path" />
<meta name="robots" content="index, follow" />

Step 3: Add Social Graph Tags

<meta property="og:title" content="Primary Keyword — Site Name" />
<meta property="og:description" content="Compelling description." />
<meta property="og:image" content="https://example.com/og-image.jpg" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="646" />
<meta property="og:url" content="https://example.com/page" />
<meta property="og:type" content="website" />

Step 4: Inject Structured Data (JSON-LD)

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    { "@type": "ListItem", "position": 1, "name": "Home", "item": "https://example.com" },
    { "@type": "ListItem", "position": 2, "name": "Category", "item": "https://example.com/category" }
  ]
}
</script>

Step 5: Configure Sitemap and Robots

User-agent: *
Allow: /
Disallow: /admin/
Disallow: /api/
Disallow: /login
Sitemap: https://example.com/sitemap.xml

Step 6: Implement SSR/SSG

FrameworkMeta APISitemap API
Next.js App Routerexport const metadata: Metadataapp/sitemap.ts
Nuxt 3useHead()@nuxtjs/sitemap
AstroFrontmatter headsrc/pages/sitemap.xml.ts
SvelteKitsvelte:headsrc/routes/sitemap.xml/+server.ts

Step 7: Set Up Hreflang

<link rel="alternate" hreflang="en" href="https://example.com/page" />
<link rel="alternate" hreflang="es" href="https://example.com/es/page" />
<link rel="alternate" hreflang="x-default" href="https://example.com/page" />

Step 8: Run Audit and Fix

npx lhci collect --url=https://example.com
npx lhci assert --preset=lighthouse:recommended

Step 9: Next.js App Router Metadata API

// app/products/[id]/page.tsx
import { Metadata } from 'next'

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const product = await getProduct(params.id)

  return {
    title: `${product.name} | Store`,
    description: product.description.slice(0, 160),
    openGraph: {
      title: product.name,
      description: product.description.slice(0, 160),
      images: [{ url: product.image, width: 1200, height: 646 }],
    },
    twitter: {
      card: 'summary_large_image',
      title: product.name,
      description: product.description.slice(0, 160),
      images: [product.image],
    },
    alternates: {
      canonical: `https://example.com/products/${product.slug}`,
    },
  }
}

Step 10: JSON-LD Component (React)

function JsonLd({ data }: { data: Record<string, unknown> }) {
  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{
        __html: JSON.stringify({ '@context': 'https://schema.org', ...data }),
      }}
    />
  )
}

// Usage
<JsonLd
  data={{
    '@type': 'Product',
    name: product.name,
    image: product.image,
    offers: { '@type': 'Offer', price: product.price, priceCurrency: 'USD' },
  }}
/>

Step 11: Dynamic OG Image Generation (Next.js)

// app/api/og/route.tsx — serverless OG image
import { ImageResponse } from 'next/og'

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const title = searchParams.get('title') || 'Default Title'

  return new ImageResponse(
    (
      <div style={{
        width: 1200, height: 646,
        display: 'flex', flexDirection: 'column',
        alignItems: 'center', justifyContent: 'center',
        background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
      }}>
        <h1 style={{ fontSize: 64, color: 'white', textAlign: 'center' }}>
          {title}
        </h1>
        <p style={{ fontSize: 32, color: 'rgba(255,255,255,0.8)' }}>
          example.com
        </p>
      </div>
    ),
    { width: 1200, height: 646 }
  )
}

// Use in metadata
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  return {
    openGraph: {
      images: [{ url: `/api/og?title=${encodeURIComponent(post.title)}` }],
    },
  }
}

Step 12: Core Web Vitals Optimization for SEO

// 1. Inline critical CSS for above-the-fold content
// app/layout.tsx
import { readFileSync } from 'fs'
import { join } from 'path'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const criticalCss = readFileSync(join(process.cwd(), 'public/critical.css'), 'utf-8')
  return (
    <html>
      <head>
        <style dangerouslySetInnerHTML={{ __html: criticalCss }} />
      </head>
      <body>{children}</body>
    </html>
  )
}

// 2. LCP optimization — preload hero image
<link rel="preload" href="/hero.webp" as="image" fetchpriority="high" />

// 3. CLS prevention — set dimensions on all images
<img src={post.image} width={800} height={450} alt={post.title} />

// 4. INP optimization — avoid long tasks
// Defer non-critical interactions
function SearchButton() {
  const handleClick = async () => {
    await new Promise(requestIdleCallback)
    // heavy search operation
  }
  return <button onClick={handleClick}>Search</button>
}

Step 13: SEO Monitoring & CI Integration

# .github/workflows/seo-audit.yml
name: SEO Audit
on: [deployment]
jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npx lhci collect --url=${{ github.event.deployment.payload.url }}
      - run: npx lhci assert --preset=lighthouse:recommended
      - uses: actions/upload-artifact@v4
        with:
          name: lighthouse-report
          path: .lighthouseci/
# Crawl and validate structured data
npx structured-data-testing-tool crawl https://example.com --max-urls=50

# Check for broken links
npx broken-link-checker https://example.com --recursive

# Validate sitemap
curl -s https://example.com/sitemap.xml | xmllint --noout -

Step 14: Headless CMS SEO Patterns

// Generate metadata from CMS content (Sanity, Contentful, Strapi)
interface SEOMetadata {
  title: string
  description: string
  ogImage?: { url: string }
  canonicalOverride?: string
  noindex?: boolean
  structuredData?: Record<string, unknown>
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const page = await getPageFromCMS(params.slug)

  return {
    title: `${page.seo.title} | Site`,
    description: page.seo.description,
    robots: page.seo.noindex ? { index: false } : { index: true },
    alternates: { canonical: page.seo.canonicalOverride || undefined },
    openGraph: page.seo.ogImage ? {
      images: [{ url: page.seo.ogImage.url, width: 1200, height: 646 }],
    } : undefined,
  }
}

// BreadcrumbList from CMS navigation
// app/breadcrumb-jsonld.tsx
function BreadcrumbJsonLd({ path }: { path: { name: string; slug: string }[] }) {
  return (
    <JsonLd data={{
      '@type': 'BreadcrumbList',
      itemListElement: path.map((item, i) => ({
        '@type': 'ListItem',
        position: i + 1,
        name: item.name,
        item: `https://example.com/${item.slug}`,
      })),
    }} />
  )
}

Step 15: Advanced Structured Data Patterns

<!-- FAQ Schema (rich snippet with expandable Q&A) -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [{
    "@type": "Question",
    "name": "How do I reset my password?",
    "acceptedAnswer": {
      "@type": "Answer",
      "text": "Go to Settings > Security > Reset Password."
    }
  }]
}
</script>

<!-- HowTo Schema (step-by-step, shown in search results) -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "HowTo",
  "name": "Install Widget",
  "step": [{
    "@type": "HowToStep",
    "position": 1,
    "text": "Download the package.",
    "url": "https://example.com/guide#step1"
  }]
}
</script>

<!-- LocalBusiness Schema -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "LocalBusiness",
  "name": "Example Store",
  "address": { "@type": "PostalAddress", "streetAddress": "123 Main St", "addressLocality": "City" },
  "telephone": "+1-123-456-7890",
  "openingHours": "Mo-Fr 09:00-17:00",
  "priceRange": "$"
}
</script>

Common Pitfalls

  1. Duplicate titles/descriptions: Every page must have unique values.
  2. Missing canonical on paginated pages: Each pagination page needs its own canonical.
  3. Blocking CSS/JS in robots.txt: Google needs CSS/JS for rendering assessment.
  4. Relative URLs in canonical/sitemap: Always use absolute URLs.
  5. Client-only rendering for crawlers: Googlebot executes JS but has a budget.
  6. Overly large sitemaps: Max 50,000 URLs or 50 MB per sitemap.
  7. No lastmod in sitemap: Helps Google understand freshness.
  8. Missing alt text on images: Accessibility and SEO issue.

Compared With

AspectSSR/SSGClient-side (CSR)Hybrid
Crawler visibilityFull contentLimited (budget)Full content
Meta tag controlServer-sideClient injectionServer-side
Sitemap generationFramework APIManualFramework API
Core Web VitalsGood (fast FCP)VariableGood
ComplexityHigherLowerMedium
Use caseContent sitesAppsBoth
Structured dataServer-sideClient injectionServer-side
HreflangFramework APIManualFramework API

Performance Considerations

  • SSR improves LCP by 40-60% compared to CSR for content pages
  • JSON-LD in <head> adds ~1-5KB to HTML size
  • Sitemap generation: static at build time preferred over dynamic generation
  • Preconnect to CDN for OG images reduces LCP font/hero image delay
  • Inline critical CSS for above-the-fold content improves FCP
  • Proper cache headers on SSG pages enables CDN caching (s-maxage)
  • Dynamic OG images via serverless functions add ~100-200ms to first request (warm)
  • Sitemap index for sites with >50K URLs prevents timeout on crawlers
  • robots.txt should be static (generated at build) — dynamic adds latency for every crawler request

Accessibility Considerations

  • Alt text on images is both an SEO ranking factor and accessibility requirement
  • Structured data does not affect visual accessibility but helps users find content
  • Descriptive page titles help screen reader users navigate between tabs
  • Skip-to-content links improve both accessibility and SEO (content reachable)
  • Heading hierarchy (h1→h2→h3) is a ranking factor and screen reader navigation aid
  • ARIA landmarks (nav, main, footer) help crawlers understand page structure

Security Considerations

  • JSON-LD can include URLs — ensure they are HTTPS and not open redirects
  • Meta tags cannot be exploited for XSS (they are HTML-encoded by default)
  • Sitemap URLs should not expose unpublished or sensitive content
  • Noindex is a crawl directive, not a security measure — use authentication for private pages
  • Dynamic OG image endpoints must sanitize input to prevent SSRF
  • Revalidation tokens for ISR sitemaps must be kept secret

Rules

  • Never use noindex on public pages unless explicitly requested.
  • Always set canonical URL to the preferred version.
  • Never block CSS or JS files in robots.txt.
  • Always use absolute URLs in canonical tags, sitemaps, and JSON-LD.
  • Never duplicate hreflang tags — one per language variant.
  • Always use application/ld+json for structured data.
  • Never include session IDs or tracking params in canonical URLs.
  • Always validate JSON-LD with Google Rich Results Test before deploying.
  • Generate sitemaps at build time for SSG sites — dynamic sitemaps add latency and cost.
  • Use sitemap index files (>50K URLs) to respect crawler limits.
  • Set lastmod on every sitemap URL to help crawlers prioritize recrawl.

References

  • references/meta-tags.md — Meta Tags Reference
  • references/performance-seo.md — Performance SEO Reference
  • references/seo-testing.md — SEO Testing Reference
  • references/sitemap.md — Sitemap Reference
  • references/ssr-seo.md — SSR/SSG SEO Reference
  • references/structured-data.md — Structured Data Reference
  • references/seo-technical-audit.md — Technical SEO Audit Reference
  • references/seo-structured-data.md — Structured Data Reference

Handoff

No artifact produced unless requested. Next skill: frontend-pwa (if the app needs offline PWA capabilities alongside SEO) Carry forward: Canonical URL pattern, current meta tags, JSON-LD schema decisions

Skills enregistrées