mcpField
N
nextjs-seo-optimizer

nextjs-seo-optimizer

github.com/kumbajirajkumar123マーケティング・グロース273 回インストール40 週間前に更新
リポジトリ

nextjs-seo-optimizer について

Next.js アプリの SEO を実装・監査する開発者向けスキルです。メタデータ、クロール制御、構造化データ、パフォーマンス、検証を扱い、App Router と従来の Pages Router の両方を案内します。

主な機能

  1. 01静的・動的メタデータ
  2. 02サイトマップと robots.txt の設定
  3. 03JSON-LD 構造化データ
  4. 04Core Web Vitals の改善案内
  5. 05SEO 監査と検証チェックリスト

活用シーン

  1. 01静的・動的ページの SEO メタデータ設定
  2. 02サイトマップとクローラー規則の作成
  3. 03コンテンツページへの構造化データ追加
  4. 04テクニカル SEO と性能の監査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/kumbajirajkumar123/nextjs-seo-optimizer --skill nextjs-seo-optimizer

インストール後、Claude Code で/nextjs-seo-optimizerを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

.gitignore
LICENSE
CONTRIBUTING.md
CHANGELOG.md
README.md
INSTALLATION.md
.github/workflows/
validate.yml
release.yml
references/
complete-example.md
next-sitemap-guide.md
performance-optimization.md
pages-router-seo.md
scripts/
validate-seo.js

詳細

Next.js SEO オプティマイザー

適切なメタデータ、サイトマップ、robots.txt、構造化データを使用して、Next.js アプリケーションを検索エンジン向けに最適化します。

SEO 実装の基本戦略

最大限の効果を得るには、次の順序で進めてください。

  1. Metadata API の設定(基盤) - タイトル、説明、Open Graph
  2. サイトマップの生成(発見) - 検索エンジンがすべてのページを見つけられるようにします
  3. robots.txt の設定(制御) - クローラーのアクセスを管理します
  4. 構造化データ(リッチリザルト) - リッチスニペットを有効にします
  5. パフォーマンスの最適化(ランキング) - Core Web Vitals
  6. 内部リンク(アーキテクチャ) - サイト構造とリンクの流れ

1. Metadata API の実装

静的メタデータ(固定コンテンツのページ)

コンテンツが変わらないページ(ホーム、会社概要、お問い合わせ)に使用します。

// app/layout.tsx or app/page.tsx
import { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    default: 'Your Site Name',
    template: '%s | Your Site Name' // Pages will be "Page Title | Your Site Name"
  },
  description: 'Compelling 150-160 character description with keywords',
  keywords: ['keyword1', 'keyword2', 'keyword3'],
  authors: [{ name: 'Author Name', url: 'https://example.com' }],
  creator: 'Company Name',
  publisher: 'Company Name',
  metadataBase: new URL('https://yourdomain.com'),
  alternates: {
    canonical: '/',
  },
  openGraph: {
    type: 'website',
    locale: 'en_US',
    url: 'https://yourdomain.com',
    title: 'Your Site Name',
    description: 'Compelling description for social sharing',
    siteName: 'Your Site Name',
    images: [{
      url: '/og-image.jpg', // 1200x630px recommended
      width: 1200,
      height: 630,
      alt: 'Image description',
    }],
  },
  twitter: {
    card: 'summary_large_image',
    title: 'Your Site Name',
    description: 'Compelling description',
    creator: '@yourtwitterhandle',
    images: ['/twitter-image.jpg'], // 1200x628px
  },
  robots: {
    index: true,
    follow: true,
    googleBot: {
      index: true,
      follow: true,
      'max-video-preview': -1,
      'max-image-preview': 'large',
      'max-snippet': -1,
    },
  },
  verification: {
    google: 'verification-code',
    yandex: 'verification-code',
    yahoo: 'verification-code',
  },
}
動的メタデータ(データベース/CMS のコンテンツ)

ブログ記事、商品、動的ページに使用します。

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

type Props = {
  params: { slug: string }
  searchParams: { [key: string]: string | string[] | undefined }
}

export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  // Fetch data
  const post = await fetchBlogPost(params.slug)
  
  // Optionally access and extend parent metadata
  const previousImages = (await parent).openGraph?.images || []
  const previousKeywords = (await parent).keywords || []
 
  return {
    title: post.title, // Will use template from layout
    description: post.excerpt.substring(0, 160),
    keywords: [...previousKeywords, ...post.tags],
    authors: [{ name: post.author.name }],
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: 'article',
      publishedTime: post.publishedAt,
      modifiedTime: post.updatedAt,
      authors: [post.author.name],
      images: [post.coverImage, ...previousImages],
      url: `https://yourdomain.com/blog/${params.slug}`,
    },
    twitter: {
      card: 'summary_large_image',
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
    },
    alternates: {
      canonical: `https://yourdomain.com/blog/${params.slug}`,
    },
  }
}

export default function Page({ params }: Props) {
  // Page content
}
メタデータのベストプラクティス

タイトルの最適化:

  • 長さ:50~60 文字(モバイルの SERP 表示上限)
  • メインキーワードを先頭付近に含めます
  • 一貫性を保つため、テンプレートを使用します:%s | Brand Name
  • キーワードを詰め込みすぎないようにします
  • ページごとに固有のタイトルを設定します

説明文の最適化:

  • 長さ:150~160 文字(デスクトップの SERP 上限)、モバイルでは 120 文字
  • メインキーワードと関連キーワードを自然に含めます
  • クリックを促す魅力的な文章にします
  • 適切な場合は、行動を促すフレーズを含めます
  • 説明文はページごとに固有のものにします

画像の要件:

  • Open Graph:1200x630px(16:9 の比率)
  • Twitter Card:1200x628px(1.91:1 の比率)
  • ファイルサイズ:< 1MB
  • 形式:JPG または PNG
  • 説明的な代替テキストを含めます

2. サイトマップの生成

組み込みの静的サイトマップ(シンプルなサイト)

予測可能な静的ルートを持つサイトに使用します。

// app/sitemap.ts
import { MetadataRoute } from 'next'
 
export default function sitemap(): MetadataRoute.Sitemap {
  return [
    {
      url: 'https://yourdomain.com',
      lastModified: new Date(),
      changeFrequency: 'yearly',
      priority: 1,
    },
    {
      url: 'https://yourdomain.com/about',
      lastModified: new Date(),
      changeFrequency: 'monthly',
      priority: 0.8,
    },
    {
      url: 'https://yourdomain.com/blog',
      lastModified: new Date(),
      changeFrequency: 'weekly',
      priority: 0.5,
    },
  ]
}
動的サイトマップ(CMS/データベースのコンテンツ)

CMS またはデータベースからの動的コンテンツがあるサイトに使用します。

// app/sitemap.ts
import { MetadataRoute } from 'next'
 
export default async function sitemap(): MetadataRoute.Sitemap {
  // Fetch dynamic routes
  const posts = await fetch('https://api.example.com/posts').then((res) => res.json())
  const products = await fetch('https://api.example.com/products').then((res) => res.json())
  
  const postEntries: MetadataRoute.Sitemap = posts.map((post: any) => ({
    url: `https://yourdomain.com/blog/${post.slug}`,
    lastModified: new Date(post.updatedAt),
    changeFrequency: 'weekly',
    priority: 0.7,
  }))
  
  const productEntries: MetadataRoute.Sitemap = products.map((product: any) => ({
    url: `https://yourdomain.com/products/${product.slug}`,
    lastModified: new Date(product.updatedAt),
    changeFrequency: 'daily',
    priority: 0.9,
  }))
  
  return [
    {
      url: 'https://yourdomain.com',
      lastModified: new Date(),
      changeFrequency: 'yearly',
      priority: 1,
    },
    ...postEntries,
    ...productEntries,
  ]
}
next-sitemap の使用(大規模サイトに推奨)

自動生成を含む詳しい設定については、references/next-sitemap-guide.md を参照してください。

優先度の目安:

  • ホームページ:1.0
  • 主要なランディングページ:0.8~0.9
  • ブログ記事/商品:0.6~0.8
  • カテゴリーページ:0.5~0.7
  • その他のページ:0.4~0.6

更新頻度の目安:

  • always:株価、リアルタイムデータ
  • hourly:ニュースサイト、頻繁に更新されるコンテンツ
  • daily:ブログ、活発な e コマースサイト
  • weekly:一般的なブログ記事
  • monthly:機能紹介ページ
  • yearly:静的ページ(会社概要、お問い合わせ)
  • never:アーカイブされたコンテンツ

3. robots.txt の設定

基本的な robots.txt(組み込み)
// app/robots.ts
import { MetadataRoute } from 'next'
 
export default function robots(): MetadataRoute.Robots {
  return {
    rules: {
      userAgent: '*',
      allow: '/',
      disallow: ['/admin/', '/api/', '/private/'],
    },
    sitemap: 'https://yourdomain.com/sitemap.xml',
  }
}
高度な robots.txt(複数のユーザーエージェント)
// app/robots.ts
import { MetadataRoute } from 'next'
 
export default function robots(): MetadataRoute.Robots {
  const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'https://yourdomain.com'
  
  return {
    rules: [
      {
        userAgent: '*',
        allow: '/',
        disallow: ['/admin/', '/api/', '/_next/', '/private/'],
        crawlDelay: 1,
      },
      {
        userAgent: 'Googlebot',
        allow: '/',
        disallow: ['/admin/', '/private/'],
      },
      {
        userAgent: 'GPTBot', // Block AI scrapers if desired
        disallow: ['/'],
      },
    ],
    sitemap: `${baseUrl}/sitemap.xml`,
    host: baseUrl,
  }
}

クロールを拒否する一般的なパス:

  • /admin/ - 管理パネル
  • /api/ - API エンドポイント(公開されている場合を除く)
  • /_next/ - Next.js の内部ファイル
  • /private/ - 非公開/制限付きコンテンツ
  • /search?* - 検索結果ページ(重複コンテンツを防止)
  • /cart/ - ショッピングカートのページ
  • /checkout/ - チェックアウトページ
  • *?* - クエリパラメーターを含むすべてのページ(任意。慎重に使用してください)

環境別のルール:

const isDevelopment = process.env.NODE_ENV === 'development'
const isStaging = process.env.VERCEL_ENV === 'preview'

if (isDevelopment || isStaging) {
  return {
    rules: {
      userAgent: '*',
      disallow: '/', // Block all indexing in dev/staging
    },
  }
}

4. 構造化データ(JSON-LD)

検索結果をリッチに表示するため、構造化データを追加します。必ず https://search.google.com/test/rich-results でテストしてください。

Organization スキーマ(ホームページ)
// app/page.tsx or components/StructuredData.tsx
export default function HomePage() {
  const organizationSchema = {
    '@context': 'https://schema.org',
    '@type': 'Organization',
    name: 'Your Company Name',
    url: 'https://yourdomain.com',
    logo: 'https://yourdomain.com/logo.png',
    description: 'Company description',
    address: {
      '@type': 'PostalAddress',
      streetAddress: '123 Main St',
      addressLocality: 'City',
      addressRegion: 'State',
      postalCode: '12345',
      addressCountry: 'US',
    },
    contactPoint: {
      '@type': 'ContactPoint',
      telephone: '+1-234-567-8900',
      contactType: 'Customer Service',
    },
    sameAs: [
      'https://twitter.com/yourcompany',
      'https://facebook.com/yourcompany',
      'https://linkedin.com/company/yourcompany',
    ],
  }

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema) }}
      />
      {/* Page content */}
    </>
  )
}
Article スキーマ(ブログ記事)
export default function BlogPost({ post }: { post: Post }) {
  const articleSchema = {
    '@context': 'https://schema.org',
    '@type': 'BlogPosting',
    headline: post.title,
    description: post.excerpt,
    image: post.coverImage,
    author: {
      '@type': 'Person',
      name: post.author.name,
      url: `https://yourdomain.com/authors/${post.author.slug}`,
    },
    publisher: {
      '@type': 'Organization',
      name: 'Your Company',
      logo: {
        '@type': 'ImageObject',
        url: 'https://yourdomain.com/logo.png',
      },
    },
    datePublished: post.publishedAt,
    dateModified: post.updatedAt,
    mainEntityOfPage: {
      '@type': 'WebPage',
      '@id': `https://yourdomain.com/blog/${post.slug}`,
    },
  }

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(articleSchema) }}
      />
      {/* Article content */}
    </>
  )
}
Product スキーマ(e コマース)
const productSchema = {
  '@context': 'https://schema.org',
  '@type': 'Product',
  name: product.name,
  image: product.images,
  description: product.description,
  sku: product.sku,
  brand: {
    '@type': 'Brand',
    name: product.brand,
  },
  offers: {
    '@type': 'Offer',
    url: `https://yourdomain.com/products/${product.slug}`,
    priceCurrency: 'USD',
    price: product.price,
    availability: product.inStock
      ? 'https://schema.org/InStock'
      : 'https://schema.org/OutOfStock',
    seller: {
      '@type': 'Organization',
      name: 'Your Company',
    },
  },
  aggregateRating: {
    '@type': 'AggregateRating',
    ratingValue: product.rating,
    reviewCount: product.reviewCount,
  },
}
FAQ スキーマ
const faqSchema = {
  '@context': 'https://schema.org',
  '@type': 'FAQPage',
  mainEntity: faqs.map((faq) => ({
    '@type': 'Question',
    name: faq.question,
    acceptedAnswer: {
      '@type': 'Answer',
      text: faq.answer,
    },
  })),
}
Breadcrumb スキーマ
const breadcrumbSchema = {
  '@context': 'https://schema.org',
  '@type': 'BreadcrumbList',
  itemListElement: breadcrumbs.map((crumb, index) => ({
    '@type': 'ListItem',
    position: index + 1,
    name: crumb.name,
    item: `https://yourdomain.com${crumb.path}`,
  })),
}

5. Core Web Vitals の最適化

これらは検索ランキングに直接影響します。詳しい戦略については、references/performance-optimization.md を参照してください。

すぐにできる改善:

  • Next.js の <Image> コンポーネントを使用します(自動最適化)
  • next.config.js で experimental.optimizePackageImports を有効にします
  • 適切なキャッシュヘッダーを設定します
  • 重いコンポーネントには動的インポートを使用します:const HeavyComponent = dynamic(() => import('./HeavyComponent'))
  • クライアントコンポーネント('use client')の使用を最小限にします
  • 適切なローディング状態を実装します

6. SEO 監査チェックリスト

SEO を監査または実装する際は、次の項目を確認してください。

メタデータ:

  • すべてのページに固有のタイトルが設定されている(50~60 文字)
  • すべてのページに固有の説明が設定されている(150~160 文字)
  • ルートレイアウトに metadataBase が設定されている
  • すべてのページに正規 URL が設定されている
  • Open Graph 画像(1200x630px)
  • Twitter カード画像(1200x628px)
  • ページ間でメタデータが重複していない

テクニカル SEO:

  • sitemap.xml に /sitemap.xml からアクセスできる
  • robots.txt に /robots.txt からアクセスできる
  • サイトマップにすべての公開ページが含まれている
  • robots.txt でサイトマップを参照している
  • 重要なページが誤ってブロックされていない
  • HTTPS が有効化され、強制されている
  • 404 ページが 404 ステータスコードを返す
  • リダイレクトで 301(恒久的)を適切に使用している

構造化データ:

  • ホームページに Organization スキーマが設定されている
  • ブログ記事に Article スキーマが設定されている
  • 商品ページに Product スキーマが設定されている(該当する場合)
  • 階層構造のあるページに Breadcrumb スキーマが設定されている
  • JSON-LD が有効である(Google リッチリザルトテストで確認)

パフォーマンス:

  • LCP < 2.5s(最大コンテンツの描画)
  • FID < 100ms(初回入力遅延)
  • CLS < 0.1(累積レイアウトシフト)
  • 画像が最適化されている(WebP、適切なサイズ)
  • クリティカル CSS がインライン化されている
  • JavaScript バンドルが 100KB 未満(初期読み込み)

コンテンツ:

  • 見出し階層が適切である(各ページに H1 が 1 つ)
  • 内部リンク戦略が実装されている
  • すべての画像に代替テキストがある
  • モバイル対応デザインである
  • アクセシビリティに対応している(WCAG 2.1 AA)

7. よくある SEO の問題と修正方法

問題:メタデータの重複

  • 修正:ページごとに固有のデータを指定して generateMetadata を使用する
  • 修正:適切な正規 URL を実装する

問題:Core Web Vitals のスコアが低い

  • 修正:Next.js Image コンポーネントを使用する
  • 修正:コード分割を実装する
  • 修正:SWC の圧縮を有効にする
  • references/performance-optimization.md を参照してください

問題:構造化データがない

  • 修正:該当するページタイプに JSON-LD を追加する
  • 修正:Google リッチリザルトテストで確認する

問題:robots.txt が重要なページを誤ってブロックしている

  • 修正:Google Search Console の robots.txt テスターで確認する
  • 修正:disallow ルールを慎重に確認する

問題:サイトマップが更新されない

  • 修正:データを取得する動的サイトマップを使用する
  • 修正:自動生成に next-sitemap を実装する

8. テストと検証ツール

SEO の実装を検証するには、次のツールを使用してください。

Google のツール:

  • Google Search Console - 検索パフォーマンスを監視する
  • Google リッチリザルトテスト - 構造化データを検証する
  • PageSpeed Insights - Core Web Vitals をテストする
  • Lighthouse - 包括的な監査を行う

サードパーティのツール:

  • Screaming Frog - サイトをクロールして問題を特定する
  • Ahrefs Site Audit - テクニカル SEO の問題を確認する
  • SEMrush Site Audit - 包括的な SEO 分析を行う
  • Schema Markup Validator - JSON-LD をテストする

テストコマンド:

# Test metadata locally
curl -I http://localhost:3000/page-to-test

# View sitemap
curl http://localhost:3000/sitemap.xml

# View robots.txt
curl http://localhost:3000/robots.txt

# Run Lighthouse audit
npx lighthouse http://localhost:3000 --view

実装の優先順位

新規プロジェクトの場合:

  1. ルートレイアウトに metadataBase を設定する
  2. すべてのページに基本的なメタデータを追加する
  3. sitemap.ts を作成する
  4. robots.ts を作成する
  5. 重要なページに構造化データを追加する
  6. パフォーマンスを最適化する
  7. 包括的なテストを実装する

既存プロジェクトの場合:

  1. 現在の SEO を監査する(Lighthouse を使用)
  2. 重大な問題を修正する(robots.txt の破損、サイトマップの欠落)
  3. 不足しているメタデータを追加する
  4. 構造化データを実装する
  5. Core Web Vitals を最適化する
  6. Google Search Console で監視する

Next.js のバージョン互換性

このスキルは次に対応しています。

  • ✅ Next.js 13+(App Router)- すべての機能
  • ⚠️ Next.js 12(Pages Router)- 旧来のパターンを使用してください。references/pages-router-seo.md を参照
  • ❌ Next.js < 12 - アップグレードを推奨します

クイックスタートコマンド

# Validate sitemap locally
curl http://localhost:3000/sitemap.xml | head -n 20

# Test robots.txt
curl http://localhost:3000/robots.txt

# Run SEO audit
npm run build && npm start
# Then in browser: DevTools > Lighthouse > SEO audit

# Test metadata extraction
curl -s http://localhost:3000 | grep -E '<title>|<meta'

環境変数

.env.local に次の変数を設定します。

NEXT_PUBLIC_BASE_URL=https://yourdomain.com
NEXT_PUBLIC_SITE_NAME="Your Site Name"
NEXT_PUBLIC_OG_IMAGE=https://yourdomain.com/og-image.jpg
GOOGLE_VERIFICATION_CODE=your-verification-code

追加リソース

特定のシナリオや高度なパターンについては、次を参照してください。

  • next-sitemap の設定:references/next-sitemap-guide.md を参照
  • パフォーマンスの最適化:references/performance-optimization.md を参照
  • Pages Router の移行:references/pages-router-seo.md を参照

コメント