メタデータシステム
メタデータとは?
メタデータは、ウェブページのコンテンツを説明する情報で、HTMLの<head>タグ内に含まれており、SEOやソーシャルメディア共有にとって非常に重要です。
例:
<head>
<title>Nexty.dev - Build and ship your SaaS faster</title>
<meta name="description" content="Next.js SaaS starter template..." />
<meta property="og:title" content="Nexty.dev" />
<meta property="og:image" content="https://nexty.dev/og.png" />
<link rel="canonical" href="https://nexty.dev/" />
<link rel="alternate" hreflang="en-US" href="https://nexty.dev/" />
<link rel="alternate" hreflang="zh-CN" href="https://nexty.dev/zh" />
</head>Nexty.devメタデータシステム
コア機能:constructMetadata()
場所:lib/metadata.ts
目的:
- すべてのページのメタデータを統一的に生成
- 多言語を自動的に処理
- カノニカルタグとhreflangタグを自動生成
- Open GraphとTwitterカードを自動生成
関数パラメータ
type MetadataProps = {
page?: string // ページ名(非推奨、設定不要)
title?: string // ページタイトル
description?: string // ページ説明
images?: string[] // OG画像(相対パスまたは絶対パス)
noIndex?: boolean // インデックスを防ぐかどうか(デフォルトはfalse)
locale?: Locale // 現在の言語(en、zh、ja)
path?: string // ページパス(/で始まる必要がある)
canonicalUrl?: string // カノニカルURL(オプション)
availableLocales?: string[] // 利用可能な言語リスト(オプション)
useDefaultOgImage?: boolean // デフォルトはtrue
}1. title - ページタイトル
title: "About Us"生成ルール:
- ホームページ:
{title} - {siteConfig.tagLine}- 例:
Nexty.dev - Build and ship your SaaS faster
- 例:
- その他のページ:
{title} | {siteConfig.name}- 例:
About Us | Nexty.dev
- 例:
指定しない場合、多言語ファイルのデフォルト値が使用されます(Home名前空間のtitle)。
2. description - ページ説明
description: "Learn more about our mission and team."目的:
- 検索結果に表示される
- ソーシャルメディアカードに表示される
指定しない場合、多言語ファイルのデフォルト値が使用されます。
3. images - OG画像
images: post.featuredImageUrl ? [post.featuredImageUrl] : [],サポートされている形式:
- 相対パス:
["images/about.png"]→https://nexty.dev/images/about.png - 絶対パス:
["https://cdn.example.com/about.png"]
ほとんどの場合、個別のページに画像を設定する必要はありません。システムはページの言語に基づいてデフォルト画像を自動的に選択します。例えば:
- 英語:
/og.png - 中国語:
/og_zh.png - 日本語:
/og_ja.png
4. noIndex - インデックスを防ぐ
noIndex: true目的:
<meta name="robots" content="noindex, nofollow" />を生成- 検索エンジンにページをインデックスしないよう指示
使用例:
- プレミアムコンテンツページ
- ログイン後のみアクセス可能なページ
- テストページ
- 404ページ
5. locale - 現在の言語
現在の言語識別子を渡すことで、メタデータ内の言語情報と必要な多言語URLを自動的に処理します。
locale: "zh" // en、zh、ja目的:
- URL生成に影響
- OG画像の選択に影響
<html lang="zh">属性に影響
6. path - ページパス
pathは/で始まる必要があり、言語プレフィックスを含めてはいけません。例えば:
path: "/about"constructMetadata()は、localeとpathを組み合わせて多言語情報とURLを自動的に処理します。
目的:
- カノニカルURLの生成に使用され、検索エンジンがページの正規版を識別し、重複コンテンツの問題を回避するのに役立つ
- hreflangリンクの生成に使用され、検索エンジンがページの言語と地域版を理解し、異なる地域のユーザーに正しい言語版を提供できるようにする
7. canonicalUrl - カノニカルURL
canonicalUrl: "/blogs"使用例:
複数のURLが同じコンテンツを指している場合、例えば/blogsと/blogs?ref=docsの場合、検索エンジンに正規版のURLが/blogsであることを伝える必要があります。これにより、検索エンジンがパラメータ付きURLを新しいページとして扱い、SEOの権威が分散するのを防ぎます。
通常、手動設定は不要で、システムは自動的にpathを参照して設定します。手動設定が必要なのは、現在のページを別のURLに向ける必要がある場合のみです。
8. availableLocales - 利用可能な言語リスト
availableLocales: ["en", "zh"] // 英語と中国語版のみ利用可能使用例:
ページにすべての言語版がない場合(ブログ記事など)。
目的:
- 存在する言語版に対してのみhreflangリンクを生成
- 404ページへのリンク生成を回避
例:ブログ記事
// どの言語にこの記事があるかを検出
const availableLocales: string[] = []
for (const checkLocale of LOCALES) {
const post = await getPostBySlug(slug, checkLocale)
if (post) availableLocales.push(checkLocale)
}
// 存在する言語に対してのみhreflangを生成
constructMetadata({
// ...
availableLocales: availableLocales.length > 0 ? availableLocales : undefined,
})このパラメータを指定しない場合、すべての言語に対してhreflangリンクが生成されます。
9. useDefaultOgImage - デフォルトOG画像を使用するかどうか
useDefaultOgImageはデフォルトでtrueで、constructMetadataから自動的にマッチングされたOG Imageを使用することを意味します。
動的ページでコンテンツに基づいてOG Imageのスタイルとコンテンツを自動生成したい場合は、useDefaultOgImageをfalseに設定する必要があります。同時に、動的ページと同じディレクトリにopengraph-image.tsxファイルを作成し、コンテンツに適応したOG Imageスタイルを開発します。
生成されるメタデータコンテンツ
constructMetadata()は以下のコンテンツを生成します:
1. 基本メタタグ
<title>About Us | Nexty.dev</title>
<meta name="description" content="Learn more about our mission..." />
<meta name="keywords" content="" />
<meta name="author" content="nexty.dev" />2. カノニカルURL
<link rel="canonical" href="https://nexty.dev/about" />3. 多言語hreflang
<link rel="alternate" hreflang="en-US" href="https://nexty.dev/about" />
<link rel="alternate" hreflang="zh-CN" href="https://nexty.dev/zh/about" />
<link rel="alternate" hreflang="ja-JP" href="https://nexty.dev/ja/about" />
<link rel="alternate" hreflang="x-default" href="https://nexty.dev/about" />x-defaultの目的:
- ユーザーの言語が一致しない場合に、どのバージョンを表示するかを検索エンジンに伝える
- 通常はデフォルト言語(英語)を指す
4. Open Graphタグ
<meta property="og:type" content="website" />
<meta property="og:title" content="About Us | Nexty.dev" />
<meta property="og:description" content="Learn more about..." />
<meta property="og:url" content="https://nexty.dev/about" />
<meta property="og:site_name" content="Nexty.dev" />
<meta property="og:locale" content="en" />
<meta property="og:image" content="https://nexty.dev/og.png" />
<meta property="og:image:alt" content="About Us | Nexty.dev" />5. Twitter Cardタグ
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="About Us | Nexty.dev" />
<meta name="twitter:description" content="Learn more about..." />
<meta name="twitter:site" content="https://nexty.dev/about" />
<meta name="twitter:image" content="https://nexty.dev/og.png" />
<meta name="twitter:creator" content="@judewei_dev" />6. Robotsタグ
<!-- noIndex = false(デフォルト) -->
<meta name="robots" content="index, follow" />
<!-- noIndex = true -->
<meta name="robots" content="noindex, nofollow" />constructMetadata()使用例
シナリオ1:ホームページ
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: 'Home' })
return constructMetadata({
title: t('title'), // 多言語ファイルから読み込み
description: t('description'), // 多言語ファイルから読み込み
locale: locale as Locale, // 現在の言語識別子を渡して、メタデータ内の言語情報と必要な多言語URLを自動処理
path: '/', // ホームページパス
})
}シナリオ2:通常ページ
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: 'About' })
return constructMetadata({
title: t('title'),
description: t('description'),
locale: locale as Locale,
path: `/about`,
})
}シナリオ3:カスタムOG画像の使用
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: 'Pricing' })
return constructMetadata({
title: t('title'),
description: t('description'),
locale: locale as Locale,
path: `/pricing`,
images: ['images/og/pricing.png'], // カスタムOG画像
})
}シナリオ4:動的OG画像
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { slug } = await params;
const result = await getProductBySlug(slug);
if (!result.success || !result.data) {
return constructMetadata({
title: "404",
description: "Product not found",
noIndex: true,
path: `/product/${slug}`,
});
}
const product = result.data;
const fullPath = `/product/${slug}`;
return constructMetadata({
title: product.name,
description: product.tagline,
path: fullPath,
useDefaultOgImage: false, // 🔑 重要:デフォルト画像を無効化
// Next.jsは自動的に同じディレクトリのopengraph-image.tsxファイルを検索して適用します。
})
}シナリオ5:ブログ記事(多言語)
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale, slug } = await params
const { post } = await getPostBySlug(slug, locale)
if (!post) {
return constructMetadata({
title: "404",
description: "Page not found",
noIndex: true, // 404ページはインデックスしない
locale: locale as Locale,
path: `/blogs/${slug}`,
})
}
// どの言語にこの記事があるかを検出
const availableLocales: string[] = []
for (const checkLocale of LOCALES) {
const { post: localePost } = await getPostBySlug(slug, checkLocale)
if (localePost) {
availableLocales.push(checkLocale)
}
}
return constructMetadata({
title: post.title,
description: post.description,
images: post.featuredImageUrl ? [post.featuredImageUrl] : [],
locale: locale as Locale,
path: `/blogs/${post.slug.replace(/^\//, '')}`,
availableLocales: availableLocales.length > 0 ? availableLocales : undefined,
})
}シナリオ6:プレミアム/制限付きコンテンツ
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale } = await params
return constructMetadata({
title: "Premium Dashboard",
description: "Access your premium features.",
locale: locale as Locale,
path: `/dashboard/premium`,
noIndex: true, // 検索エンジンにプレミアムコンテンツをインデックスさせない
})
}シナリオ7:カノニカルで複数のURLを統合
// シナリオ1:/free-trialと/pricingが同じコンテンツを指しているが、検索エンジンに/pricingを正規URLとして認識させたい
// シナリオ2:/blogsと/blogs?ref=docsが同じページを指しているため、検索エンジンに/blogsが正規URLであることを伝える必要がある
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale } = await params
return constructMetadata({
title: "Start Your Free Trial",
description: "Try all features for free.",
locale: locale as Locale,
path: "/free-trial",
canonicalUrl: "/pricing", // 正規URLとして/pricingを指定
})
}シナリオ8:外部CDNでOG画像を使用
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale } = await params
return constructMetadata({
title: "Case Study",
description: "How we helped Company X achieve results.",
locale: locale as Locale,
path: "/case-studies/company-x",
images: [
"https://cdn.yourdomain.com/case-studies/company-x-og.png" // 絶対URL
],
})
}