sitemapとは何か
sitemapは検索エンジンクローラーに対してサイトの構造や更新頻度を伝えるXMLファイルです。特に以下のようなケースで威力を発揮します。
- 新しいページが頻繁に追加されるブログやECサイト
- 深い階層構造を持つサイト
- 外部リンクが少なく、クローラーが発見しにくいページがあるサイト
基本的な静的sitemap
最もシンプルな実装から始めましょう。小規模なアプリケーションでは、sitemap.xml
ファイルをapp
ディレクトリのルートに配置できます。
<!-- app/sitemap.xml -->
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com</loc>
<lastmod>2024-06-22T00:00:00.000Z</lastmod>
<changefreq>yearly</changefreq>
<priority>1</priority>
</url>
<url>
<loc>https://example.com/about</loc>
<lastmod>2024-06-22T00:00:00.000Z</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
</urlset>
この方法は手軽ですが、ページが増えるたびに手動で更新する必要があります。
動的Sitemapの実装
実際のプロジェクトでは、動的にsitemapを生成する方が現実的です。
sitemap.ts
を使用して自動でsitemapを生成できます。
// app/sitemap.ts
import type { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://example.com',
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 1,
},
{
url: 'https://example.com/about',
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8,
},
{
url: 'https://example.com/blog',
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 0.5,
},
]
}
これでhttps://example.com/sitemap.xml
にアクセスすると、適切なXMLが生成されます。
ブログサイトでの実践例
ブログサイトを例に、より実践的な実装を見ていきましょう。
// app/sitemap.ts
import type { MetadataRoute } from 'next'
// ブログ記事を取得する関数(例)
async function getBlogPosts() {
// 実際のプロジェクトでは、CMSやデータベースから取得
const posts = await fetch('https://api.example.com/posts').then(res => res.json())
return posts
}
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = 'https://example.com'
// 静的ページ
const staticPages: MetadataRoute.Sitemap = [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
{
url: `${baseUrl}/about`,
lastModified: new Date('2024-01-01'),
changeFrequency: 'monthly',
priority: 0.8,
},
{
url: `${baseUrl}/contact`,
lastModified: new Date('2024-01-01'),
changeFrequency: 'yearly',
priority: 0.5,
},
]
// 動的ページ(ブログ記事)
const posts = await getBlogPosts()
const blogPages: MetadataRoute.Sitemap = posts.map((post: any) => ({
url: `${baseUrl}/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
changeFrequency: 'weekly' as const,
priority: 0.7,
}))
return [...staticPages, ...blogPages]
}
この実装により、新しいブログ記事が追加されるたびに自動的にsitemapに反映されます。
画像Sitemapの活用
画像を多く使用するサイトでは、画像Sitemapを活用できます。これにより、Google画像検索での表示機会を増やせます。
// app/sitemap.ts
import type { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://example.com/gallery',
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 0.8,
images: [
'https://example.com/images/photo1.jpg',
'https://example.com/images/photo2.jpg',
'https://example.com/images/photo3.jpg',
],
},
]
}
多言語サイトでの実装
多言語サイトでは、alternates
プロパティを使用してhreflang属性を追加できます。
// app/sitemap.ts
import type { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://example.com',
lastModified: new Date(),
alternates: {
languages: {
ja: 'https://example.com/ja',
en: 'https://example.com/en',
zh: 'https://example.com/zh',
},
},
},
{
url: 'https://example.com/about',
lastModified: new Date(),
alternates: {
languages: {
ja: 'https://example.com/ja/about',
en: 'https://example.com/en/about',
zh: 'https://example.com/zh/about',
},
},
},
]
}
大規模サイト向け:複数Sitemapの生成
大規模なWebアプリケーションでは、sitemapを複数のファイルに分割する必要があります。Googleの制限により、1つのsitemapには最大50,000のURLしか含められません。
// app/sitemap.ts
import type { MetadataRoute } from 'next'
export async function generateSitemaps() {
// 商品の総数を取得し、必要なsitemap数を計算
const totalProducts = await getProductCount()
const sitemapCount = Math.ceil(totalProducts / 50000)
return Array.from({ length: sitemapCount }, (_, i) => ({ id: i }))
}
export default async function sitemap({
id,
}: {
id: number
}): Promise<MetadataRoute.Sitemap> {
const start = id * 50000
const end = start + 50000
const products = await getProducts({
limit: 50000,
offset: start,
})
return products.map((product) => ({
url: `https://example.com/products/${product.slug}`,
lastModified: new Date(product.updatedAt),
changeFrequency: 'weekly' as const,
priority: 0.6,
}))
}
これにより、/sitemap/0.xml
、/sitemap/1.xml
といった形で複数のsitemapが生成されます。
パフォーマンス最適化のポイント
キャッシュの活用
sitemap.jsは特別なRoute Handlerで、Dynamic APIやdynamic config optionを使用しない限り、デフォルトでキャッシュされます。
// app/sitemap.ts
import type { MetadataRoute } from 'next'
// 静的データのみを使用する場合、自動的にキャッシュされる
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://example.com',
lastModified: new Date('2024-01-01'), // 固定日付
changeFrequency: 'yearly',
priority: 1,
},
]
}
効率的なデータ取得
大量のデータを扱う場合は、必要最小限の情報のみを取得しましょう。
// 効率的なデータ取得の例
async function getPostsForSitemap() {
// 必要な列のみを取得
const posts = await db.post.findMany({
select: {
slug: true,
updatedAt: true,
},
where: {
published: true,
},
})
return posts
}
実装時の注意点
URLの正規化
URLは必ず完全なURL(プロトコル含む)で指定し、末尾のスラッシュは統一しましょう。
// 良い例
url: 'https://example.com/about'
// 悪い例
url: '/about' // プロトコルとドメインが不足
url: 'example.com/about' // プロトコルが不足
lastModifiedの適切な設定
lastModifiedは実際の更新日時を反映させることが重要です。
{
url: 'https://example.com/blog/example-post',
lastModified: post.updatedAt, // 実際の更新日時
// lastModified: new Date(), // 常に現在時刻は避ける
}
priorityとchangeFrequencyの適切な設定
priorityは相対的な重要度を示し、changeFrequencyは実際の更新頻度に合わせて設定します。
// ホームページ:最重要、年1回程度の更新
{ priority: 1.0, changeFrequency: 'yearly' }
// ブログ記事:重要、週1回程度の更新
{ priority: 0.7, changeFrequency: 'weekly' }
// アーカイブページ:普通、更新なし
{ priority: 0.3, changeFrequency: 'never' }
Google Search Consoleでの確認
sitemapの実装が完了したら、Google Search Consoleで正しく認識されているか確認しましょう。
- Google Search Consoleにログイン
- 対象のプロパティを選択
- 左メニューの「サイトマップ」をクリック
- sitemap.xmlのURLを入力して送信
エラーが発生した場合は、生成されたXMLを直接確認し、構文エラーがないかチェックしてください。
まとめ
Next.js App Routerでのsitemap実装は、静的ファイルから動的生成まで柔軟に対応できる強力な機能です。小規模サイトでは静的XML、中規模サイトでは動的生成、大規模サイトでは複数sitemap生成と、プロジェクトの規模に応じて適切な手法を選択しましょう。
重要なのは、検索エンジンとユーザーの両方にとって価値のあるsitemapを作成することです。定期的にGoogle Search Consoleでインデックス状況を確認し、必要に応じて改善を重ねていくことが、効果的なSEO対策につながります。
参考サイト