はじめに
「SEO対策って何から始めればいいんだろう」
Next.js App Router を使ってプロダクトを開発していると、いつかこの疑問にぶつかります。メタデータの設定、構造化データ、sitemap.xml、robots.txt... やるべきことは分かっていても、どこから手をつけていいか迷うものです。
私自身、複数のプロダクトを開発してきましたが、SEO対策はどうしても後回しにしがちでした。でも実際に取り組んでみると、Next.js App Router には SEO 対策を効率的に行うための仕組みが整っていることが分かりました。
この記事では、IdeaSpool と ZERONOVA LAB サイトで実際に行った SEO 最適化の全体像を、具体的なコード例とともに解説します。
この記事で扱う内容
- メタデータの設定 — 基本的な metadata オブジェクトの使い方
- OGP(Open Graph Protocol) — SNS でシェアされたときの表示を最適化
- 構造化データ(JSON-LD) — 検索結果でのリッチスニペット表示
- sitemap.xml — クローラーにページ一覧を伝える
- robots.txt — クロールの制御
1. メタデータの基本設定
Next.js App Router では、metadata オブジェクトを export するだけでメタデータを設定できます。
2026年1月17日、ZERONOVA LAB サイトの初期構築時の開発日記にはこう書きました:
Next.js App Router での OGP 設定は
metadataオブジェクトで簡潔に書ける
layout.tsx にサイト全体のデフォルトメタデータを設定します:
// app/layout.tsx
export const metadata: Metadata = {
metadataBase: new URL("https://your-site.com"),
title: {
default: "サイト名",
template: "%s | サイト名",
},
description: "サイトの説明文",
};
ここで重要なのが metadataBase です。
2026年1月26日の IdeaSpool 開発日記に、私はこう記録しています:
Next.js の metadataBase を設定しないと OGP の URL が相対パスになる問題がある
metadataBase を設定しないと、OGP 画像の URL が /og-image.png のような相対パスになってしまい、SNS でシェアしたときに画像が表示されません。必ず絶対 URL で指定しましょう。
ページ固有のメタデータ
各ページの page.tsx で、ページ固有のメタデータを設定できます:
// app/products/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
const product = getProductBySlug(params.slug);
return {
title: product.name,
description: product.description,
openGraph: {
title: product.name,
description: product.description,
},
};
}
generateMetadata は非同期関数なので、データベースから情報を取得してメタデータを動的に生成することもできます。
2. OGP(Open Graph Protocol)の設定
OGP は、SNS でリンクを共有したときに表示されるカード情報を制御するプロトコルです。
export const metadata: Metadata = {
openGraph: {
type: "website",
locale: "ja_JP",
url: "https://your-site.com",
siteName: "サイト名",
title: "ページタイトル",
description: "ページの説明",
images: [
{
url: "/og-image.png",
width: 1200,
height: 630,
alt: "OGP画像の説明",
},
],
},
twitter: {
card: "summary_large_image",
title: "ページタイトル",
description: "ページの説明",
images: ["/og-image.png"],
},
};
OGP 画像のサイズ
OGP 画像は 1200×630px が推奨サイズです。このサイズであれば、X(Twitter)、Facebook、LINE など主要な SNS で最適に表示されます。
画像は public/ ディレクトリに配置すれば、/og-image.png のようにルートからアクセスできます。
3. 構造化データ(JSON-LD)
構造化データは、検索エンジンにページの内容を機械的に理解してもらうためのマークアップです。
2026年1月29日、Focus Blog に構造化データを実装したときの開発日記にはこう書いています:
構造化データは SEO の基本。早めに実装しておくべき
ブログ記事の構造化データ
ブログ記事には BlogPosting スキーマを使います:
// app/journal/[slug]/page.tsx
const jsonLd = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: post.title,
description: post.description,
datePublished: post.date,
author: {
"@type": "Person",
name: "著者名",
},
publisher: {
"@type": "Organization",
name: "サイト名",
},
};
export default function Page() {
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
{/* ページ本文 */}
</>
);
}
パンくずリストの構造化データ
パンくずリストも構造化データで表現できます:
const breadcrumbJsonLd = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{
"@type": "ListItem",
position: 1,
name: "ホーム",
item: "https://your-site.com",
},
{
"@type": "ListItem",
position: 2,
name: "ブログ",
item: "https://your-site.com/blog",
},
{
"@type": "ListItem",
position: 3,
name: post.title,
},
],
};
JSON-LD の配置場所
開発日記にはこう記録しています:
JSON-LD は layout.tsx に書くと全ページに適用される
サイト全体に適用したい構造化データ(WebSite、Organization など)は layout.tsx に、ページ固有のデータ(BlogPosting など)は各 page.tsx に配置するのが良い設計です。
4. sitemap.xml の動的生成
sitemap.xml は、サイト内のページ一覧をクローラーに伝えるファイルです。
Next.js App Router では sitemap.ts を作成するだけで動的に生成できます:
// app/sitemap.ts
import { MetadataRoute } from "next";
export default function sitemap(): MetadataRoute.Sitemap {
const staticPages = [
{ url: "https://your-site.com", priority: 1.0 },
{ url: "https://your-site.com/about", priority: 0.8 },
];
// 動的ページ(ブログ記事など)
const posts = getAllPosts();
const postPages = posts.map((post) => ({
url: `https://your-site.com/blog/${post.slug}`,
lastModified: post.updatedAt || post.date,
priority: 0.7,
}));
return [...staticPages, ...postPages];
}
2026年1月17日の開発日記:
robots.tsとsitemap.tsで動的生成すると、コンテンツ追加時に自動更新される
これが Next.js App Router の大きなメリットです。ブログ記事を追加するたびに sitemap.xml を手動で更新する必要がありません。
sitemap の priority 設計
priority はページの相対的な重要度を示します(0.0〜1.0):
| ページ種別 | priority | 理由 |
|---|---|---|
| トップページ | 1.0 | サイトの顔 |
| 主要ランディングページ | 0.9 | コンバージョンに直結 |
| 記事一覧 | 0.8 | ハブページ |
| 個別記事 | 0.7 | メインコンテンツ |
| タグページ | 0.6 | 補助的なナビゲーション |
5. robots.txt によるクロール制御
robots.txt は、クローラーにどのページをクロールして良いか伝えるファイルです。
// app/robots.ts
import { MetadataRoute } from "next";
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: "*",
allow: "/",
disallow: ["/api/", "/auth/", "/dashboard/"],
},
sitemap: "https://your-site.com/sitemap.xml",
};
}
2026年1月26日、IdeaSpool の SEO 最適化時の開発日記:
robots.ts: クローラー制御(認証ページを除外)
認証ページ(/auth/)や API エンドポイント(/api/)、ダッシュボード(/dashboard/)などはインデックス不要です。これらを disallow に指定することで、クローラーの効率的なクロールを促せます。
実践: IdeaSpool での SEO 最適化
ここからは、IdeaSpool で実際に行った SEO 最適化の流れを紹介します。
課題の認識
IdeaSpool は、アイデアを収集・整理するプロダクトです。リリース後しばらくは SEO 対策をほとんど行っていませんでした。検索流入がほぼゼロだったのです。
実装の流れ
2026年1月26日に一気に SEO 最適化を実施しました。開発日記から引用します:
SEO 最適化を実装
- robots.ts: クローラー制御(認証ページを除外)
- sitemap.ts: 静的ページ + 公開投稿ページの動的生成
- JSON-LD 構造化データ(WebSite, SoftwareApplication, Organization)
- 各ページにメタデータ追加
- metadataBase 設定で絶対 URL 解決
sitemap の動的生成で悩んだこと
開発日記にはこう書いています:
sitemap の生成
- 公開投稿ページは slug が必要
- → Supabase から公開プロジェクトを取得して動的生成
IdeaSpool では、ユーザーが作成した公開プロジェクトも sitemap に含めたいと考えました。しかし、それには Supabase からデータを取得する必要があります。
最終的に、sitemap.ts 内で Supabase から公開プロジェクトを取得し、動的に sitemap を生成する形に落ち着きました:
// 概念的なコード
export default async function sitemap() {
const publicProjects = await getPublicProjects();
return [
...staticPages,
...publicProjects.map((p) => ({
url: `https://ideaspool.app/p/${p.slug}`,
lastModified: p.updatedAt,
})),
];
}
ZERONOVA LAB での実践
ZERONOVA LAB サイトでも同様の SEO 最適化を行いました。
ブログの構造化データ
2026年1月29日、Focus Blog に構造化データを追加しました:
構造化データ(JSON-LD)を追加(BlogPosting、Blog、CollectionPage、BreadcrumbList)
ブログでは複数のスキーマタイプを使い分けています:
| ページ種別 | スキーマタイプ |
|---|---|
| 記事一覧 | Blog + BreadcrumbList |
| 個別記事 | BlogPosting + BreadcrumbList |
| タグ一覧 | CollectionPage + BreadcrumbList |
タグページの SEO 効果
タグページ(/blog/tags/[tag])を作成したことで、カテゴリごとの記事一覧が検索でヒットするようになりました。
開発日記から:
タグページを追加(
/focus/blog/tags/[tag])
タグページは「SEO 的に弱い」と思われがちですが、適切に設計すれば十分なロングテールキーワードを狙えます。
よくある失敗と対策
1. metadataBase を設定し忘れる
症状: OGP 画像が表示されない
原因: OGP 画像の URL が相対パスになっている
対策: layout.tsx で metadataBase を必ず設定する
2. 認証ページが検索結果に表示される
症状: /login や /signup が検索結果に出る
原因: robots.txt で除外していない
対策: robots.ts で認証関連のパスを disallow に追加
3. 構造化データのエラー
症状: Search Console で構造化データのエラーが報告される
原因: 必須フィールドの欠落、型の不一致
対策: Google のリッチリザルトテスト で事前に検証
SEO 最適化のチェックリスト
最後に、Next.js App Router での SEO 対策チェックリストをまとめます:
基本設定
-
metadataBaseを設定している -
titleとdescriptionを各ページに設定している - OGP 画像(1200×630px)を用意している
構造化データ
- サイト全体の
WebSiteスキーマを設定している - 記事ページに
BlogPostingスキーマを設定している - パンくずリストに
BreadcrumbListスキーマを設定している
クローラー対策
-
sitemap.tsで動的に sitemap を生成している -
robots.tsで認証ページ等を除外している - Search Console に sitemap を登録している
まとめ
Next.js App Router を使えば、SEO 対策は驚くほど簡単に実装できます。
- メタデータ:
metadataオブジェクトで宣言的に設定 - 構造化データ: JSON-LD を
<script>タグで埋め込む - sitemap / robots:
.tsファイルで動的に生成
「SEO は後でやろう」と思いがちですが、早めに基盤を作っておくと、コンテンツを追加するたびに自動で最適化されます。
この記事が、Next.js で SEO 対策を始める方の参考になれば幸いです。
関連記事として、Next.js ブログに構造化データ(JSON-LD)を実装する方法 では BlogPosting や BreadcrumbList などの構造化データ実装を詳しく解説しています。また、Next.js で Markdown ブログを構築する方法 では App Router で Markdown ブログを一から作る手順を紹介しています。
Zeronova(ゼロノバ)
Product Manager / AI-Native Builder
Web/IT業界19年以上・20以上のWebサービスを担当したPdM。東証プライム上場企業の子会社代表として事業経営を経験。現在はAIを駆使して企画から実装まで完結させる個人開発を実践中。