Claude
Skills
Sign in
Back

seo-2025-patterns

Included with Lifetime
$97 forever

2025 SEO best practices for Next.js including Core Web Vitals (INP replaces FID), E-E-A-T signals, Schema markup, AI content guidelines, and technical SEO. Use when optimizing pages for search engines, implementing metadata, adding structured data, or improving page speed.

Ads & Marketingscripts

What this skill does


# SEO 2025 Patterns

**Purpose:** Implement 2025 SEO best practices for Next.js applications to maximize search visibility and organic traffic.

**Activation Triggers:**
- Optimizing pages for search engines
- Implementing metadata and Open Graph
- Adding Schema.org structured data
- Improving Core Web Vitals
- E-E-A-T signal implementation
- Sitemap and robots.txt setup
- Meta description optimization

**Key Resources:**
- `scripts/seo-audit.sh` - Comprehensive SEO audit script
- `scripts/validate-schema.sh` - Validate JSON-LD structured data
- `templates/metadata-patterns.tsx` - Next.js Metadata API patterns
- `templates/schema-components.tsx` - JSON-LD schema components
- `examples/complete-seo-setup.md` - Full SEO implementation example

## 2025 SEO Landscape

### Key Changes from 2024

1. **INP Replaces FID** - Interaction to Next Paint is now a Core Web Vital
2. **AI Content Guidelines** - Google rewards helpful AI content with human oversight
3. **E-E-A-T Enhanced** - "Experience" added as first factor
4. **Passage Ranking** - Google indexes specific passages
5. **Video SEO** - Video snippets dominate SERPs
6. **Zero-Click Optimization** - Featured snippets and AI Overviews

### Core Web Vitals Targets (2025)

| Metric | Target | What It Measures |
|--------|--------|------------------|
| LCP | < 2.5s | Largest Contentful Paint |
| INP | < 200ms | Interaction to Next Paint |
| CLS | < 0.1 | Cumulative Layout Shift |

## Next.js Metadata API

### Basic Metadata Configuration

```typescript
// app/layout.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  metadataBase: new URL('https://example.com'),
  title: {
    default: 'Site Name - Main Tagline',
    template: '%s | Site Name',
  },
  description: 'Your site description for search engines (150-160 characters)',
  keywords: ['keyword1', 'keyword2', 'keyword3'],
  authors: [{ name: 'Author Name', url: 'https://author.com' }],
  creator: 'Creator Name',
  publisher: 'Publisher Name',
  formatDetection: {
    email: false,
    address: false,
    telephone: false,
  },
}
```

### Open Graph Configuration

```typescript
export const metadata: Metadata = {
  openGraph: {
    type: 'website',
    locale: 'en_US',
    url: 'https://example.com',
    siteName: 'Site Name',
    title: 'Page Title for Social Sharing',
    description: 'Description for social media (150-200 chars)',
    images: [
      {
        url: '/og-image.png',
        width: 1200,
        height: 630,
        alt: 'OG Image Alt Text',
      },
    ],
  },
}
```

### Twitter Card Configuration

```typescript
export const metadata: Metadata = {
  twitter: {
    card: 'summary_large_image',
    site: '@sitehandle',
    creator: '@creatorhandle',
    title: 'Title for Twitter',
    description: 'Description for Twitter (150-200 chars)',
    images: ['/twitter-image.png'],
  },
}
```

### Robots Configuration

```typescript
export const metadata: Metadata = {
  robots: {
    index: true,
    follow: true,
    nocache: false,
    googleBot: {
      index: true,
      follow: true,
      'max-video-preview': -1,
      'max-image-preview': 'large',
      'max-snippet': -1,
    },
  },
}
```

### Dynamic Metadata

```typescript
// app/blog/[slug]/page.tsx
import type { Metadata, ResolvingMetadata } from 'next'

type Props = {
  params: { slug: string }
}

export async function generateMetadata(
  { params }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  const post = await getPost(params.slug)

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: 'article',
      publishedTime: post.publishedAt,
      modifiedTime: post.updatedAt,
      authors: [post.author.name],
      images: [
        {
          url: post.coverImage,
          width: 1200,
          height: 630,
          alt: post.title,
        },
      ],
    },
  }
}
```

## Dynamic Sitemap

```typescript
// app/sitemap.ts
import { MetadataRoute } from 'next'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = 'https://example.com'

  // Static pages
  const staticPages: MetadataRoute.Sitemap = [
    {
      url: baseUrl,
      lastModified: new Date(),
      changeFrequency: 'weekly',
      priority: 1,
    },
    {
      url: `${baseUrl}/about`,
      lastModified: new Date(),
      changeFrequency: 'monthly',
      priority: 0.8,
    },
    {
      url: `${baseUrl}/pricing`,
      lastModified: new Date(),
      changeFrequency: 'monthly',
      priority: 0.9,
    },
  ]

  // Dynamic pages from database
  const posts = await getAllPosts()
  const postPages: MetadataRoute.Sitemap = posts.map((post) => ({
    url: `${baseUrl}/blog/${post.slug}`,
    lastModified: new Date(post.updatedAt),
    changeFrequency: 'weekly' as const,
    priority: 0.6,
  }))

  return [...staticPages, ...postPages]
}
```

## Robots.txt

```typescript
// app/robots.ts
import { MetadataRoute } from 'next'

export default function robots(): MetadataRoute.Robots {
  const baseUrl = 'https://example.com'

  return {
    rules: [
      {
        userAgent: '*',
        allow: '/',
        disallow: ['/api/', '/admin/', '/_next/', '/private/'],
      },
      {
        userAgent: 'GPTBot',
        disallow: '/', // Block AI training crawlers if desired
      },
    ],
    sitemap: `${baseUrl}/sitemap.xml`,
  }
}
```

## Schema.org Structured Data

### Organization Schema

```typescript
// components/seo/OrganizationSchema.tsx
export function OrganizationSchema() {
  const schema = {
    '@context': 'https://schema.org',
    '@type': 'Organization',
    name: 'Company Name',
    url: 'https://example.com',
    logo: 'https://example.com/logo.png',
    sameAs: [
      'https://twitter.com/company',
      'https://linkedin.com/company/company',
      'https://github.com/company',
    ],
    contactPoint: {
      '@type': 'ContactPoint',
      telephone: '+1-555-555-5555',
      contactType: 'customer service',
      availableLanguage: ['English'],
    },
  }

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
    />
  )
}
```

### Article Schema

```typescript
// components/seo/ArticleSchema.tsx
interface ArticleSchemaProps {
  title: string
  description: string
  image: string
  author: { name: string; url: string }
  publishedAt: string
  updatedAt: string
  url: string
}

export function ArticleSchema(props: ArticleSchemaProps) {
  const schema = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: props.title,
    description: props.description,
    image: props.image,
    author: {
      '@type': 'Person',
      name: props.author.name,
      url: props.author.url,
    },
    publisher: {
      '@type': 'Organization',
      name: 'Company Name',
      logo: {
        '@type': 'ImageObject',
        url: 'https://example.com/logo.png',
      },
    },
    datePublished: props.publishedAt,
    dateModified: props.updatedAt,
    mainEntityOfPage: {
      '@type': 'WebPage',
      '@id': props.url,
    },
  }

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
    />
  )
}
```

### FAQ Schema

```typescript
// components/seo/FAQSchema.tsx
interface FAQItem {
  question: string
  answer: string
}

export function FAQSchema({ items }: { items: FAQItem[] }) {
  const schema = {
    '@context': 'https://schema.org',
    '@type': 'FAQPage',
    mainEntity: items.map((item) => ({
      '@type': 'Question',
      name: item.question,
      acceptedAnswer: {
        '@type': 'Answer',
        text: item.answer,
      },
    })),
  }

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
    />
  )
}
```

### Breadcrumb Schema

```typescript
// components/seo/BreadcrumbSchema.tsx
interface BreadcrumbI

Related in Ads & Marketing