🔍 進階 SEO 実戦:動的 Metadata と Open Graph

もしあなたが超クールなブログやECサイトを作ったのに、FacebookやLINEで共有したときに醜いURLだけが表示され、美しいサムネイルとタイトルが表示されないなら、クリック率は確実に悲惨なものになるでしょう。

従来のReact(クライアントサイドレンダリング)時代では、この問題を解決するのは非常に苦痛で、プリレンダリングのために追加のサーバーやパッケージに依存する必要がありました。 しかし、Next.js App Routerの世界では、SEO最適化が組み込まれており、信じられないほど簡単に実装できます!

この章では、Metadata APIを徹底的にマスターし、動的にタイトルや説明文を生成する方法、そして自動的にソーシャル共有プレビュー画像(Open Graph Image)を生成する方法を教えます。


1. 静的 Metadata 設定

ホームページや特定の固定ページにタイトルを設定するだけなら、そのページのpage.tsxまたはlayout.tsxmetadata定数をエクスポートするだけで済みます。

// src/app/layout.tsx または src/app/page.tsx
import { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'Vibe Tutor 知識課金プラットフォーム',
  description: '台湾で最も強力なSaaS実戦開発コース。Next.jsでネット印鑑機を構築する方法を教えます!',
  keywords: ['Next.js', 'React', 'SaaS', '教育', 'Vibe Tutor'],
  authors: [{ name: 'Vibe Coder' }],
  // Open Graph 設定(Facebook、LINE用)
  openGraph: {
    title: 'Vibe Tutor 知識課金プラットフォーム',
    description: '台湾で最も強力なSaaS実戦開発コース',
    url: 'https://vibe-tutor.com',
    siteName: 'Vibe Tutor',
    images: [
      {
        url: 'https://vibe-tutor.com/og-image.jpg',
        width: 1200,
        height: 630,
        alt: 'Vibe Tutor コースプレビュー画像',
      },
    ],
    locale: 'zh_TW',
    type: 'website',
  },
  // Twitter 設定
  twitter: {
    card: 'summary_large_image',
    title: 'Vibe Tutor 知識課金プラットフォーム',
    description: '台湾で最も強力なSaaS実戦開発コース',
    creator: '@vibecoder',
    images: ['https://vibe-tutor.com/twitter-image.jpg'],
  },
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  // ...
}

💡 実戦テクニック:タイトルテンプレート

layout.tsxでタイトルテンプレートを設定すると、子ページで毎回ブランド名を繰り返す必要がなくなります:

// src/app/layout.tsx
export const metadata: Metadata = {
  title: {
    template: '%s | Vibe Tutor', // 子ページのタイトルが%sを置換
    default: 'Vibe Tutor 知識課金プラットフォーム', // 子ページで設定がない場合に使用
  },
};

// src/app/pricing/page.tsx
export const metadata: Metadata = {
  title: '価格プラン', // 最終的なタイトルは「価格プラン | Vibe Tutor」になる
};

2. 動的 Metadata (generateMetadata)

ブログ記事ページ/blog/[slug]/page.tsxのように、各記事のタイトルが異なる場合、どうすればよいでしょうか? この場合、定数metadataは使えません。代わりに非同期関数generateMetadataをエクスポートする必要があります。

Next.jsはページをレンダリングする「前に」この関数を実行し、データベースからデータを取得して、対応する<title><meta>タグをHTMLの<head>に生成します!

// src/app/blog/[slug]/page.tsx
import { Metadata, ResolvingMetadata } from 'next';
import { db } from '@/lib/db'; // データベース

type Props = {
  params: { slug: string }
};

// 動的 Metadata 生成
export async function generateMetadata(
  { params }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  // 1. データベースから記事を検索
  const post = await db.post.findUnique({ where: { slug: params.slug } });

  // 記事が存在しない場合、404タイトルを返す
  if (!post) {
    return {
      title: '記事が見つかりません',
    };
  }

  // 親のMetadataを拡張(オプション)
  const previousImages = (await parent).openGraph?.images || [];

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      // 記事にカバー画像があれば使用、なければ親のデフォルトを使用
      images: post.coverImage ? [post.coverImage] : previousImages, 
    },
  };
}

export default async function BlogPostPage({ params }: Props) {
  // 実際のページコンテンツのレンダリングロジック
  const post = await db.post.findUnique({ where: { slug: params.slug } });
  // ...
}

💡 パフォーマンスの心配?Next.jsが解決済み!

generateMetadataでデータベースを検索し、BlogPostPageでも同じ検索をするのはパフォーマンス的に無駄では?」と疑問に思うかもしれません。 心配無用です!Next.jsは内部でfetchキャッシュとReactのcacheメカニズムを使用しており、同じリクエスト内で同じURL/クエリは実際には一度しか実行されません。後続の呼び出しはキャッシュ結果を返すので、パフォーマンスに全く影響しません!


3. 究極の技:自動OG画像生成 (Image Generation)

VercelやGitHubの記事共有リンクで、「記事のタイトルが自動的に書かれた画像」を見たことがありますか? これは動的OG画像(Dynamic Open Graph Image)と呼ばれます。

以前はこの機能を実装するために、サードパーティAPIを利用したりHeadless Chromeでスクリーンショットを撮る必要があり、複雑すぎて諦めたくなるものでした。 今では、Next.jsがネイティブで@vercel/ogをサポートしており、JSXを書くだけで自動的に画像に変換してくれます!

ステップ1:opengraph-image.tsxファイルを作成

ルートフォルダ(例:src/app/blog/[slug]/)に、opengraph-image.tsx(または.alt.txt / .tsxImageResponseの組み合わせ)という特別なファイルを作成します。

// src/app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og';
import { db } from '@/lib/db';

// 画像サイズの設定
export const runtime = 'edge';
export const alt = 'Blog Post Cover Image';
export const size = {
  width: 1200,
  height: 630,
};
export const contentType = 'image/png';

export default async function Image({ params }: { params: { slug: string } }) {
  // 記事タイトルを取得
  const post = await db.post.findUnique({ where: { slug: params.slug } });
  const title = post?.title || '素晴らしい記事';

  return new ImageResponse(
    (
      // ここにJSXを書く!画像に変換されます!
      <div
        style={{
          fontSize: 80,
          background: 'linear-gradient(to bottom right, #4f46e5, #ec4899)', // グラデーション
          color: 'white',
          width: '100%',
          height: '100%',
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'center',
          justifyContent: 'center',
          padding: '80px',
          textAlign: 'center',
          fontWeight: 800,
        }}
      >
        {/* データベースから取得した動的タイトルを表示 */}
        <div style={{ marginBottom: 40 }}>{title}</div>
        
        {/* ブランドロゴやテキストを追加 */}
        <div style={{ fontSize: 40, color: 'rgba(255,255,255,0.8)' }}>
          🚀 Vibe Tutor 実戦教育
        </div>
      </div>
    ),
    {
      ...size,
    }
  );
}

これだけです!設定不要で、誰かがFacebookに記事URLを貼り付けると: https://your-domain.com/blog/my-awesome-post Facebookのクローラーが自動的にこのJSXでリアルタイム生成されたグラデーション背景+大タイトルの美しい画像を読み取ります!

これがNext.jsがSEOとビジネスサイトに最適と言われる究極の理由です!MetadataとOG Imageをマスターすれば、あなたのサイトのトラフィックは確実に天井知らずになります!📈

完全なチュートリアルをロック解除

このチャプターは有料コンテンツです。プロジェクトに参加して、10以上の神レベルのPromptや実際のソースコード例を含む、5000字以上の深い分析をロック解除してください!