nextjs-seo-optimizer について
Next.js アプリの SEO を実装・監査する開発者向けスキルです。メタデータ、クロール制御、構造化データ、パフォーマンス、検証を扱い、App Router と従来の Pages Router の両方を案内します。
主な機能
- 01静的・動的メタデータ
- 02サイトマップと robots.txt の設定
- 03JSON-LD 構造化データ
- 04Core Web Vitals の改善案内
- 05SEO 監査と検証チェックリスト
活用シーン
- 01静的・動的ページの SEO メタデータ設定
- 02サイトマップとクローラー規則の作成
- 03コンテンツページへの構造化データ追加
- 04テクニカル SEO と性能の監査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ kumbajirajkumar123/ nextjs-seo-optimizer --skill nextjs-seo-optimizerインストール後、Claude Code で/nextjs-seo-optimizerを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.gitignoreLICENSECONTRIBUTING.mdCHANGELOG.mdREADME.mdINSTALLATION.md.github/workflows/validate.ymlrelease.ymlreferences/complete-example.mdnext-sitemap-guide.mdperformance-optimization.mdpages-router-seo.mdscripts/validate-seo.js詳細
Next.js SEO オプティマイザー
適切なメタデータ、サイトマップ、robots.txt、構造化データを使用して、Next.js アプリケーションを検索エンジン向けに最適化します。
SEO 実装の基本戦略
最大限の効果を得るには、次の順序で進めてください。
- Metadata API の設定(基盤) - タイトル、説明、Open Graph
- サイトマップの生成(発見) - 検索エンジンがすべてのページを見つけられるようにします
- robots.txt の設定(制御) - クローラーのアクセスを管理します
- 構造化データ(リッチリザルト) - リッチスニペットを有効にします
- パフォーマンスの最適化(ランキング) - Core Web Vitals
- 内部リンク(アーキテクチャ) - サイト構造とリンクの流れ
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
実装の優先順位
新規プロジェクトの場合:
- ルートレイアウトに metadataBase を設定する
- すべてのページに基本的なメタデータを追加する
- sitemap.ts を作成する
- robots.ts を作成する
- 重要なページに構造化データを追加する
- パフォーマンスを最適化する
- 包括的なテストを実装する
既存プロジェクトの場合:
- 現在の SEO を監査する(Lighthouse を使用)
- 重大な問題を修正する(robots.txt の破損、サイトマップの欠落)
- 不足しているメタデータを追加する
- 構造化データを実装する
- Core Web Vitals を最適化する
- 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 を参照