add-seo

tech-with-seth's avatarfrom tech-with-seth

Add SEO meta tags, Open Graph tags, and structured data to routes. Use when optimizing pages for search engines or social sharing.

⭐1stars🔀0forks📁View on GitHub🕐Updated Jan 10, 2026

When & Why to Use This Skill

This Claude skill automates the implementation of technical SEO for web applications using modern React 19 patterns. It enables developers to seamlessly integrate essential meta tags, Open Graph data, Twitter cards, and Schema.org structured data (JSON-LD) into their routes, significantly improving search engine visibility and social media preview quality.

Use Cases

  • Dynamic Page Metadata: Automatically generate unique, data-driven titles and meta descriptions for product pages or blog posts using React 19 native JSX tags.
  • Social Media Optimization: Configure Open Graph and Twitter Card metadata to ensure rich, engaging previews when website links are shared across social platforms.
  • Rich Snippet Integration: Implement JSON-LD structured data for Products, Organizations, and Breadcrumbs to help search engines better understand content and display rich search results.
  • Crawler Management: Easily manage search engine indexing by adding robots 'noindex' tags to private or sensitive routes to prevent them from appearing in search results.
nameadd-seo
descriptionAdd SEO meta tags, Open Graph tags, and structured data to routes. Use when optimizing pages for search engines or social sharing.

Add SEO

Adds SEO optimization including meta tags, Open Graph, Twitter cards, and structured data using React 19 patterns.

When to Use

  • Adding page titles and descriptions
  • Setting up Open Graph for social sharing
  • Adding Twitter card metadata
  • Implementing structured data (JSON-LD)
  • User asks to "add SEO", "add meta tags", or "optimize for search"

React 19 Meta Pattern

Use native JSX tags, NOT meta() export.

import type { Route } from './+types/product';

export default function ProductPage({ loaderData }: Route.ComponentProps) {
    const { product } = loaderData;

    return (
        <>
            {/* Required */}
            <title>{product.name} | Iridium</title>
            <meta name="description" content={product.description} />

            {/* Open Graph */}
            <meta property="og:title" content={product.name} />
            <meta property="og:description" content={product.description} />
            <meta property="og:image" content={product.imageUrl} />
            <meta property="og:type" content="product" />
            <meta property="og:url" content={`https://example.com/products/${product.id}`} />

            {/* Twitter Card */}
            <meta name="twitter:card" content="summary_large_image" />
            <meta name="twitter:title" content={product.name} />
            <meta name="twitter:description" content={product.description} />
            <meta name="twitter:image" content={product.imageUrl} />

            {/* Page content */}
            <Container>
                <h1>{product.name}</h1>
            </Container>
        </>
    );
}

Basic Page SEO

export default function AboutPage() {
    return (
        <>
            <title>About Us | Iridium</title>
            <meta name="description" content="Learn about our mission, team, and values." />
            <meta name="robots" content="index, follow" />

            <Container>
                {/* Page content */}
            </Container>
        </>
    );
}

Dynamic SEO from Loader Data

import type { Route } from './+types/blog-post';

export async function loader({ params }: Route.LoaderArgs) {
    const post = await getPost(params.slug);
    return { post };
}

export default function BlogPost({ loaderData }: Route.ComponentProps) {
    const { post } = loaderData;
    const canonicalUrl = `https://example.com/blog/${post.slug}`;

    return (
        <>
            <title>{post.title} | Blog | Iridium</title>
            <meta name="description" content={post.excerpt} />
            <link rel="canonical" href={canonicalUrl} />

            {/* Article-specific Open Graph */}
            <meta property="og:type" content="article" />
            <meta property="og:title" content={post.title} />
            <meta property="og:description" content={post.excerpt} />
            <meta property="og:image" content={post.featuredImage} />
            <meta property="og:url" content={canonicalUrl} />
            <meta property="article:published_time" content={post.publishedAt} />
            <meta property="article:author" content={post.author.name} />

            <Container>
                <article>
                    <h1>{post.title}</h1>
                    {/* Post content */}
                </article>
            </Container>
        </>
    );
}

Structured Data (JSON-LD)

import type { Route } from './+types/product';

export default function ProductPage({ loaderData }: Route.ComponentProps) {
    const { product } = loaderData;

    const structuredData = {
        '@context': 'https://schema.org',
        '@type': 'Product',
        name: product.name,
        description: product.description,
        image: product.imageUrl,
        offers: {
            '@type': 'Offer',
            price: product.price,
            priceCurrency: 'USD',
            availability: 'https://schema.org/InStock',
        },
    };

    return (
        <>
            <title>{product.name} | Iridium</title>
            <meta name="description" content={product.description} />

            {/* Structured Data */}
            <script
                type="application/ld+json"
                dangerouslySetInnerHTML={{
                    __html: JSON.stringify(structuredData),
                }}
            />

            <Container>
                {/* Product content */}
            </Container>
        </>
    );
}

Organization Structured Data

const organizationData = {
    '@context': 'https://schema.org',
    '@type': 'Organization',
    name: 'Iridium',
    url: 'https://example.com',
    logo: 'https://example.com/logo.png',
    sameAs: [
        'https://twitter.com/iridium',
        'https://github.com/iridium',
    ],
};
const breadcrumbData = {
    '@context': 'https://schema.org',
    '@type': 'BreadcrumbList',
    itemListElement: [
        {
            '@type': 'ListItem',
            position: 1,
            name: 'Home',
            item: 'https://example.com',
        },
        {
            '@type': 'ListItem',
            position: 2,
            name: 'Products',
            item: 'https://example.com/products',
        },
        {
            '@type': 'ListItem',
            position: 3,
            name: product.name,
            item: `https://example.com/products/${product.id}`,
        },
    ],
};

No-Index Pages

For pages that shouldn't appear in search:

export default function PrivatePage() {
    return (
        <>
            <title>Private Page | Iridium</title>
            <meta name="robots" content="noindex, nofollow" />
            {/* Content */}
        </>
    );
}

SEO Checklist

  • Unique <title> for each page (50-60 chars)
  • <meta name="description"> (150-160 chars)
  • Open Graph tags for social sharing
  • Twitter Card tags
  • Canonical URL for duplicate content
  • Structured data where appropriate
  • No-index on private pages

Anti-Patterns

  • Using meta() export (old pattern)
  • Duplicate titles across pages
  • Missing descriptions
  • Descriptions over 160 characters
  • Missing Open Graph image
  • Using same meta on all pages

Full Reference

See .github/instructions/seo.instructions.md for comprehensive documentation.