PR

【Next.jsブログ構築】SEOとOGPをMarkdownから自動設定する方法|generateMetadata解説

Next.js ブログに SEO と OGP を自動設定する手順を解説するイラスト。女性がノートパソコンで作業し、画面上に Google 検索結果と SNS シェアカードが綺麗に表示されているイメージを表現したアイキャッチ画像 VPS・RentalServer
この記事は約20分で読めます。
記事内に広告が含まれています。
スポンサーリンク

これまでの Next.js ブログサイト構築シリーズをまとめたページは以下の通りです。

ブログ記事をせっかく書いても、
Google の検索結果に正しく表示されなければ、読者に届けることができません。

また、 X(Twitter)や Facebook でリンクをシェアしたとき、
タイトルも画像も表示されない「素っ気ない URL」になってしまうと、
クリックしてもらえる機会を大きく逃してしまいます。

前回の第8回では、ヘッダーとフッターを実装して、
ブログの外枠となる共通レイアウトを完成させました。

今回は、ブログとして本格的に運用するために欠かせない
SEO(検索エンジン最適化)OGP(SNS シェア用の設定) を、
Markdown のフロントマターから自動で読み込んで記事ごとに設定する仕組みを構築します。

この記事を読むと、以下のことができるようになります。

  • SEO と OGP が何か、なぜ必要なのかが分かる
  • Markdown のフロントマターに書くだけで自動設定できる仕組みが作れる
  • Next.js の generateMetadata 関数の使い方が分かる
  • Google 検索結果と SNS シェアに、記事ごとの情報が正しく表示されるようになる

SEO とは何か

SEO は「Search Engine Optimization」の略で、日本語では「検索エンジン最適化」と訳します。

Google などの検索エンジンに「このページは何についての記事か」を正確に伝えて、検索結果の上位に表示されやすくするための取り組みのことです。

検索エンジンはどうやってページを見つけるのか

Google は「クローラー」と呼ばれるロボットを常時インターネット上に巡回させています。
クローラーは Web サイトを訪問してページの中身を読み取り、 Google のデータベースに登録(インデックス)していきます。

このとき、クローラーが最初に読む重要な情報が、 HTML の <head> タグの中に書かれたメタ情報です。

<head>
  <title>VPSにNode.jsをインストールしてNext.jsを初期構築する手順</title>
  <meta name="description" content="Ubuntu サーバーの VPS 環境へ Node.js をインストールし、
  Next.js の初期システムを構築する手順を解説します。">
</head>
  • <title> タグ:ブラウザのタブや、検索結果の青いリンク文字として表示されます
  • <meta name="description"> タグ:検索結果のタイトルの下に表示される説明文です

検索結果でどう見えるか

title タグと description タグが正しく設定されていると、 Google の検索結果では以下のように表示されます。

VPSにNode.jsをインストールしてNext.jsを初期構築する手順  ← title タグの内容
https://example.com/posts/vps-setup
Ubuntu サーバーの VPS 環境へ Node.js をインストールし、   ← description タグの内容
Next.js の初期システムを構築する手順を解説します。

これらが設定されていないと、 Google が勝手にページの本文から文章を抜き出して表示するため、検索ユーザーに記事の内容が正確に伝わりにくくなってしまいます。

WordPress との違い

WordPress では、Yoast SEOAll in One SEO などのプラグインが投稿画面に専用の入力欄を用意してくれていました。
タイトルと説明文を入力するだけで SEO タグを自動生成してくれていたので、あまり意識せずに済んでいた方も多いと思います。

Next.js では、そのプラグインの役割を自分でコードとして実装する必要があります。
今回実装する generateMetadata 関数が、まさに「Yoast SEO プラグイン相当の機能」を自作するイメージです。

OGP とは何か

OGP は「Open Graph Protocol」の略です。

X(Twitter)や Facebook、 LINE などの SNS でリンクをシェアしたときに、タイトル・説明文・画像をカード形式で綺麗に表示させるための仕組みです。
Facebook 社(現 Meta 社)が 2010 年に提案した規格で、現在は主要 SNS のほぼすべてが対応しています。

OGP がない場合と、ある場合の違い

OGP が設定されていないページのリンクを X でシェアすると、以下のようになります。

https://example.com/posts/vps-setup

ただの URL が貼り付けられるだけで、何の記事なのかが一切伝わりません。

OGP が正しく設定されていると、同じリンクをシェアしても以下のようなカード形式で表示されます。

┌─────────────────────────────────────────┐
│  [アイキャッチ画像]                      │
├─────────────────────────────────────────┤
│  VPSにNode.jsをインストールして          │
│  Next.jsを初期構築する手順              │
│                                         │
│  Ubuntu サーバーの VPS 環境へ Node.js を │
│  インストールし、Next.js の初期システムを│
│  構築する手順を解説します。              │
│  example.com                            │
└─────────────────────────────────────────┘

画像・タイトル・説明文が揃ったカード形式になることで、クリックしてもらえる可能性が大きく上がります。

OGP の仕組み

OGP は HTML の <head> タグ内に <meta property="og:〇〇"> という形式で書きます。
X(Twitter)は独自の twitter: プレフィックスを使うため、両方を書く必要があります。

<head>
  <!-- OGP の基本設定 -->
  <meta property="og:title"       content="記事タイトル">
  <meta property="og:description" content="記事の説明文">
  <meta property="og:image"       content="https://example.com/images/eyecatch.webp">
  <meta property="og:url"         content="https://example.com/posts/article-slug">
  <meta property="og:type"        content="article">
  <meta property="og:site_name"   content="あなたのブログ名">

  <!-- Twitter(X)専用の設定 -->
  <meta name="twitter:card"        content="summary_large_image">
  <meta name="twitter:title"       content="記事タイトル">
  <meta name="twitter:description" content="記事の説明文">
  <meta name="twitter:image"       content="https://example.com/images/eyecatch.webp">
</head>

主な OGP タグの意味は以下の通りです。

タグ役割
og:titleカードに表示されるタイトル
og:descriptionカードに表示される説明文
og:imageカードのサムネイル画像(1200×630px が推奨サイズ)
og:urlそのページの正式な URL
og:typeページの種類(article または website
og:site_nameサイト名
twitter:cardX のカード形式(summary_large_image で大きい画像表示)

なお、 OGP の画像は X や Facebook などの SNS のサーバーが <head> タグを読み取って表示するものです。
ブログのページ本文には画像は表示されません。 これは正常な動作ですので、ご安心ください。

今回の作業の全体像

今回行う作業は以下の5つのステップです。

  1. OGP 用の画像ファイルを public/images フォルダに配置する
  2. Markdown のフロントマターに descriptionogImage の項目を追加する
  3. lib/posts.ts を更新して新しい項目を読み取れるようにする
  4. 個別記事ページ(app/posts/[id]/page.tsx)に generateMetadata 関数を追加する
  5. layout.tsx のデフォルト metadata を整備する

作業完了後のフォルダ構成は以下のようになります。

example-blog/
├── app/
│   ├── layout.tsx                ← デフォルト metadata を更新
│   └── posts/
│       └── [id]/
│           └── page.tsx          ← generateMetadata 関数を追加
├── lib/
│   └── posts.ts                  ← description・ogImage を返すよう更新
├── posts/
│   ├── test-1.md                 ← frontmatter に description・ogImage を追加
│   └── test-2.md                 ← frontmatter に description のみ追加
└── public/
    └── images/
        ├── default-ogp.webp      ← 今回新しく配置(サイト共通のデフォルト画像)
        └── test-1-eyecatch.webp  ← 今回新しく配置(test-1.md 用の個別画像)

OGP 用の画像を準備する

OGP の画像は 1200×630px が SNS のサムネイルとして推奨されているサイズです。

今回は以下の2種類の画像を用意します。

ファイル名用途
default-ogp.webpogImage を指定していない記事に自動で使われるサイト共通の画像
test-1-eyecatch.webp動作確認用のテスト記事専用のアイキャッチ画像

2枚を用意する理由は、「個別画像あり」と「個別画像なしでデフォルト画像が自動適用される」という2つのパターンを両方テストできるようにするためです。

Cursor のエクスプローラーから public/images フォルダへドラッグ&ドロップでアップロードします。

アップロード後、以下のコマンドでファイルが正しく配置されたか確認します。

ls ~/example-blog/public/images/

ls は「list」の略で、指定したフォルダの中身を一覧表示するコマンドです。
default-ogp.webptest-1-eyecatch.webp の2ファイルが表示されれば完了です。

Markdown のフロントマターに項目を追加する

次に、記事の Markdown ファイル上部のフロントマターに、 SEO と OGP のための2つの項目を追加します。

フロントマターとは

Markdown ファイルの一番上にある --- で囲まれた部分のことです。
タイトルや日付など、記事のメタ情報を書いておく場所で、 gray-matter というツールが読み取ります。

test-1.md の更新

nano ~/example-blog/posts/test-1.md

黒い画面( nano エディタ)が開いたら、フロントマター部分を以下のように書き換えます。
書き換えたら Ctrl + OEnter で保存し、 Ctrl + X でエディタを終了します。

---
title: "テスト記事その1"
date: "2026-03-31"
description: "1つ目のテスト記事です。OGP個別画像の表示確認に使います。"
ogImage: "/images/test-1-eyecatch.webp"
---

test-2.md の更新

nano ~/example-blog/posts/test-2.md

黒い画面( nano エディタ)が開いたら、フロントマター部分を以下のように書き換えます。
test-2.md はあえて ogImage を書かずに保存します。
デフォルト画像が自動で使われることを確認するテストになります。

---
title: "テスト記事その2:日付の確認"
date: "2026-04-01"
description: "2つ目のテスト記事です。デフォルトOGP画像の表示確認に使います。"
---

Ctrl + OEnter で保存し、 Ctrl + X でエディタを終了します。

各項目の意味は以下の通りです。

項目役割
description検索結果や SNS カードに表示される説明文(100〜120文字が目安)
ogImageSNS シェア時のサムネイル画像のパス。/images/ファイル名 の形式で書く

lib/posts.ts を更新する

これまでの getPostData 関数は titledatecontent しか返していませんでした。
descriptionogImage も返せるように2行追記します。

nano ~/example-blog/lib/posts.ts

黒い画面が開いたら、getPostData 関数の末尾にある return の部分を以下のように書き換えます。
書き換えたら Ctrl + OEnter で保存し、 Ctrl + X でエディタを終了します。

return {
  id,
  content: matterResult.content,
  title: matterResult.data.title as string,
  date: dateString,
  description: (matterResult.data.description as string) ?? '',  // 追加
  ogImage: (matterResult.data.ogImage as string) ?? '',          // 追加
};

末尾の ?? '' は「値がなければ空文字にする」という意味です。
ogImage を frontmatter に書いていない記事でもエラーにならないようにするための安全策です。

個別記事ページに generateMetadata 関数を追加する

今回の作業で最も重要なステップです。

Next.js の App Router では、generateMetadata という関数を page.tsx に書くだけで、そのページの <head> タグの中身(タイトル・ OGP など)を記事ごとに自動生成してくれます。

nano ~/example-blog/app/posts/\[id\]/page.tsx

黒い画面が開いたら、ファイルの一番上にある既存の import 文の直下に、以下のコードを追記して保存します( Ctrl + OEnterCtrl + X )。

既存の import 文はそのままで、その下に追記するイメージです。

// ▼ 既存の import 文はそのままにして、この下に追記する
import type { Metadata } from 'next';

// サイトの情報を定数として定義しておく
// ドメインを変更する際はここを1か所書き換えるだけで全体に反映される
const SITE_URL = 'https://next.example.com';
const DEFAULT_OG_IMAGE = '/images/default-ogp.webp';

// generateMetadata:記事ごとのメタ情報を自動生成する特別な関数
// Next.js がこの名前を自動で認識して、<head> タグへの書き出しを行ってくれる
export async function generateMetadata({
  params,
}: {
  params: Promise<{ id: string }>;
}): Promise<Metadata> {
  // URL の [id] 部分(例:test-1)を取り出す
  const { id } = await params;

  // id に対応する記事データ(title・description・ogImage)を取得する
  const postData = getPostData(id);

  // frontmatter に ogImage が書かれていればそれを使い、
  // 書かれていなければデフォルト画像(default-ogp.webp)を自動で使う
  const ogImage = postData.ogImage || DEFAULT_OG_IMAGE;

  return {
    // ブラウザのタブと検索結果に表示されるタイトル
    // layout.tsx の template 設定により「記事タイトル | ブログ名」の形式になる
    title: postData.title,

    // 検索結果に表示される説明文
    description: postData.description,

    // OGP(SNS シェア用)の設定
    openGraph: {
      title: postData.title,
      description: postData.description,
      url: `${SITE_URL}/posts/${id}`,  // 記事の正式な URL
      type: 'article',                  // ページの種類(ブログ記事なので article を指定)
      images: [
        {
          // 画像は SNS サーバーが外部から取得するため、https:// から始まる絶対 URL が必要
          url: `${SITE_URL}${ogImage}`,
          width: 1200,
          height: 630,
        },
      ],
    },

    // Twitter(X)専用の設定
    twitter: {
      card: 'summary_large_image',  // 大きい画像カード形式で表示する
      title: postData.title,
      description: postData.description,
      images: [`${SITE_URL}${ogImage}`],
    },
  };
}

コードのポイント解説

generateMetadata は Next.js があらかじめ決めた特別な関数名です。
この名前で書くだけで Next.js が自動で認識し、<head> タグへの書き出しを行ってくれます。

getPostData はこのファイルに既存の import 文で読み込み済みですので、新たに追記する必要はありません。
今回新しく追記が必要な import は、1行目の import type { Metadata } from 'next'; だけです。

コードの中で asyncawait を使っているのは、 Next.js 15 以降の仕様で URL のパラメータ([id] の部分)が順番待ちの処理(非同期)で渡されるためです。
await を書くことで「id の値が届いてから次の処理に進む」という順序が保証されます。

OGP の画像 URL に https:// から始まる絶対 URL を使っているのは、 SNS のサーバーが外部からその画像を取得しに来るためです。
/images/〇〇 のような相対パスでは SNS 側が画像を見つけられないため、絶対 URL が必須です。

layout.tsx のデフォルト metadata を整備する

最後に、サイト全体のデフォルト値として layout.tsxmetadata を更新します。
個別記事ページで generateMetadata が定義されている場合は、自動的にそちらが優先されます。

nano ~/example-blog/app/layout.tsx

黒い画面が開いたら、既存の import 文の直下に定数を追記し、metadata の部分を以下のように書き換えます。
書き換えたら Ctrl + OEnter で保存し、 Ctrl + X でエディタを終了します。

import type { Metadata } from "next";
import "./globals.css";
import Header from "../components/Header";
import Footer from "../components/Footer";

// ▼ この定数を追記する
const SITE_URL = 'https://next.example.com';

export const metadata: Metadata = {
  title: {
    default: 'あなたのブログ名',        // トップページなど個別設定がないページのタイトル
    template: '%s | あなたのブログ名',  // 個別記事では「記事タイトル | ブログ名」になる
  },
  description: 'ブログの説明文をここに入力してください',
  openGraph: {
    siteName: 'あなたのブログ名',
    images: [
      {
        url: `${SITE_URL}/images/default-ogp.webp`,
        width: 1200,
        height: 630,
      },
    ],
  },
};

title.template%s は、各ページの title が自動で差し込まれる場所です。
サイト名を変更したいときは、このファイルの あなたのブログ名 を1か所書き換えるだけで全ページに反映されます。

動作確認

設定が正しく動作しているか、2つの方法で確認します。

ブラウザのタブで確認する

Next.js を起動してブラウザでアクセスし、タブの表示を確認します。

cd ~/example-blog
npm run dev
  • トップページ:タブに「あなたのブログ名」と表示されているか
  • 個別記事ページ:タブに「記事タイトル | あなたのブログ名」と表示されているか

開発者ツールで OGP タグを確認する

個別記事ページを開いた状態で F12 キーを押して開発者ツールを開き、「Elements」タブから <head> の左側にある をクリックして展開します。
以下のタグが正しく出力されていれば成功です。

<title>テスト記事その1 | あなたのブログ名</title>
<meta name="description" content="1つ目のテスト記事です。...">
<meta property="og:title" content="テスト記事その1">
<meta property="og:description" content="1つ目のテスト記事です。...">
<meta property="og:image" content="https://next.example.com/images/test-1-eyecatch.webp">
<meta property="og:url" content="https://next.example.com/posts/test-1">
<meta property="og:type" content="article">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://next.example.com/images/test-1-eyecatch.webp">

test-1.md では og:imagetest-1-eyecatch.webp になっていること、
test-2.md では og:imagedefault-ogp.webp になっていることも合わせて確認してください。

2つのパターンがどちらも正しく表示されれば、 SEO タグと OGP タグの自動設定は完璧に動作しています。
非エンジニアの私でも、コードを正しく配置するだけでここまでの仕組みが実現できたことに、率直に驚きました。

もし SITE_URL is not defined のようなエラーが表示された場合は、layout.tsx または page.tsximport 文の直下に const SITE_URL = 'https://next.example.com'; の行が追記されているかを確認してください。

今後の記事執筆時の運用ルール

この仕組みが完成したことで、今後は Markdown のフロントマターに書くだけで SEO と OGP が自動で設定されます。

---
title: "記事タイトル"
date: "2026-04-11"
description: "記事の説明文(100〜120文字が目安)"
ogImage: "/images/記事のアイキャッチ画像ファイル名.webp"
---

ogImage を省略した場合は default-ogp.webp が自動で使われます。
アイキャッチ画像を用意した記事は public/images/ にアップロードしてから ogImage に書くだけです。

WordPress の Yoast SEO プラグインで行っていた操作と、ほぼ同じ感覚で運用できるようになりました。

まとめ

今回は、 Next.js の generateMetadata 関数を使い、 Markdown のフロントマターに書いた descriptionogImage を自動で読み込んで、記事ごとに SEO タグと OGP タグを設定する仕組みを構築しました。

エンジニアではない私でも、コードの意味を一つひとつ理解しながら進めることで、問題なく実装することができました。
generateMetadata を1つ書くだけで、 Google 検索・ X(Twitter)・ Facebook のシェア表示がすべて自動化される点は、 Next.js の大きな強みだと実感しています。
今後は記事を追加するたびに手動でタグを書く必要がなくなりました。

次回は、手元の Obsidian で書き溜めた Markdown ファイルを VPS の posts フォルダへ同期して、ブログを簡単に更新できる快適な運用フローの構築を行います。

コメント

タイトルとURLをコピーしました