microCMS

microCMS + Next.jsでJamstackブログを作ってみよう(Next.js 15 ver.)

中野紘子

microCMSとNext.js 15(App Router)を組み合わせて、Jamstackなブログを作成するチュートリアルです。
Pages Routerでのチュートリアルはこちらの記事を参照してください。

準備

利用サービス

1. microCMS
APIベースの日本製ヘッドレスCMSです。ブログのコンテンツ管理を担います。
2. Vercel
フロントエンド開発向けのプラットフォームで、サイト公開におけるインフラやCI/CDを担います。

ソフトウェアバージョン

下記のバージョンで開発を行なっています。バージョンの差異によって若干機能が異なる可能性があります。

  • Next 15.0.3
  • react 19.0.0 RC
  • react-dom 19.0.0 RC
  • microcms-js-sdk 3.1.2


2024年10月21日に、Next.js 15の安定版が正式リリースされました。Next.js 15のApp Routerを使用するにはReact 19が必要なため、記事公開時はまだRC版ですが使用していきます。

1. Next.jsプロジェクトの作成

Next.jsプロジェクトのテンプレートを作成できるCLIが用意されているので、コマンドを入力してプロジェクトを作成しましょう。
Next.jsのバージョンは15を指定しています。

npx create-next-app@15.0 microcms-next15-jamstack-blog

選択式のインストーラーが起動します。今回は以下を選択していきます。

作成したプロジェクトに移動して開発サーバーを立ち上げます。

cd microcms-next15-jamstack-blog
npm run dev

http://localhost:3000にアクセスすると、以下のような画面が表示されます。

2. APIの作成

次に、microCMSでカテゴリー、ブログの順に2つのAPIを作成していきます。アカウント登録〜サービスの作成まではこちらの記事を参考にしてください。

1. API(カテゴリー)の作成

1. APIを作成

自分で決める」を選択します。

2. APIの基本情報を入力

API名:カテゴリー、エンドポイント:categoriesと入力します。

3. APIの型を選択

リスト形式」を選択します。

4. APIスキーマを定義

フィールドを追加]ボタンクリックして、各フィールドを追加していきます。下図のように「カテゴリー名(種類:テキストフィールド)」を追加していきましょう。

2. API(ブログ)の作成

1. APIを作成

自分で決める」を選択します。

2. APIの基本情報を入力

API名:ブログ、エンドポイント:blogと入力します。

3. APIの型を選択

リスト形式」を選択します。

4. APIスキーマを定義

フィールドを追加]ボタンクリックして、各フィールドを追加していきます。下図のように「タイトル(種類:テキストフィールド)」、「本文(種類:リッチエディタ)」、「カテゴリー(コンテンツ参照 - カテゴリー ※1つ前のステップで作成したAPI)」を追加していきましょう。

以上でAPIの作成は完了です。

3. コンテンツの作成

1. カテゴリーの作成

次に、microCMSでコンテンツの作成をしていきます。APIの作成が完了すると、サイドバーの「コンテンツ(API)」に、作成したAPI「ブログ」「カテゴリー」が追加されています。右上の [+追加]ボタンをクリックして、コンテンツ(カテゴリー)を追加していきます。

「カテゴリー名」にテキストを入力し、右上の[公開]ボタンをクリックします。いくつか登録しておきましょう。

2. ブログの作成

次にコンテンツ(ブログ)を追加していきます。「タイトル」と「本文」にテキストを入力し、「カテゴリー」を選択して、右上の[公開]ボタンをクリックします。ブログの一覧ページで表示したいため、適当に複数件登録しておきましょう。登録が完了したらコンテンツ一覧画面に戻り、画面の右上にある[APIプレビュー]をクリックします。


次に[取得]ボタンをクリックし、登録した内容がAPI経由で取得できるか確認してください。以下はレスポンスJSONの例です。

{
    "contents": [
        {
            "id": "9qxro-lo6",
            "createdAt": "2024-10-14T05:42:05.606Z",
            "updatedAt": "2024-10-30T09:51:21.048Z",
            "publishedAt": "2024-10-14T05:42:05.606Z",
            "revisedAt": "2024-10-30T09:51:21.048Z",
            "title": "3つめのブログ記事です",
            "body": "<p>3つめのブログ記事の本文です。</p>",
            "category": {
                "id": "outdoor",
                "createdAt": "2024-10-30T09:48:47.106Z",
                "updatedAt": "2024-10-30T09:48:47.106Z",
                "publishedAt": "2024-10-30T09:48:47.106Z",
                "revisedAt": "2024-10-30T09:48:47.106Z",
                "name": "アウトドア"
            }
        },
        {
            "id": "uw_sl-grg",
            "createdAt": "2024-10-14T05:41:53.965Z",
            "updatedAt": "2024-10-30T09:51:10.424Z",
            "publishedAt": "2024-10-14T05:41:53.965Z",
            "revisedAt": "2024-10-30T09:51:10.424Z",
            "title": "2つめのブログ記事です",
            "body": "<p>2つめのブログ記事の本文です。</p>",
            "category": {
                "id": "cooking",
                "createdAt": "2024-10-30T09:47:57.116Z",
                "updatedAt": "2024-10-30T09:47:57.116Z",
                "publishedAt": "2024-10-30T09:47:57.116Z",
                "revisedAt": "2024-10-30T09:47:57.116Z",
                "name": "料理"
            }
        },
        {
            "id": "nrtqpxxqatz",
            "createdAt": "2024-10-14T05:41:34.000Z",
            "updatedAt": "2024-10-30T09:51:00.842Z",
            "publishedAt": "2024-10-14T05:41:34.000Z",
            "revisedAt": "2024-10-30T09:51:00.842Z",
            "title": "1つめのブログ記事です",
            "body": "<p>1つめのブログ記事の本文です。</p><h1 id=\"ha879678dfe\">見出し1</h1><p>リストはこのように表示されます。</p><ul><li>リスト1</li><li>リスト2</li><li>リスト3</li></ul><h2 id=\"had7ce3f1cb\">見出し2</h2><p><strong>太字</strong>や<u>下線</u>も表示可能です。</p>",
            "category": {
                "id": "travel",
                "createdAt": "2024-10-30T09:46:55.690Z",
                "updatedAt": "2024-10-30T09:46:55.690Z",
                "publishedAt": "2024-10-30T09:46:55.690Z",
                "revisedAt": "2024-10-30T09:46:55.690Z",
                "name": "旅行"
            }
        }
    ],
    "totalCount": 3,
    "offset": 0,
    "limit": 10
}

以上でコンテンツの作成は完了です。

4. envファイルの作成

microCMSでは、リクエストにAPIキーを含めることで特定のデータを取得できます。
しかし、このAPIキーがGitHubのパブリックリポジトリで公開されてしまうと、セキュリティ上の問題が発生します。そのため、envファイルなどでAPIキーを保護しましょう

まず、ルート直下に.env.development.localファイルを作成します。
.localを付けることでローカル環境専用に、.developmentを付けることで開発環境専用に利用できます。
作成したファイルに下記の情報を入力してください。

MICROCMS_API_KEY=xxxxxxxxxx
MICROCMS_SERVICE_DOMAIN=xxxxxxxxxx

MICROCMS_API_KEY

microCMS 管理画面の「サービス設定 > API キー」から確認することができます。

MICROCMS_SERVICE_DOMAIN

microCMS 管理画面の URL(https://xxxxxxxx.microcms.io)の xxxxxxxx の部分です。


プロジェクト内で以下のように記述することで、APIキーとサービスドメインを参照することができます。

process.env.MICROCMS_API_KEY
process.env.MICROCMS_SERVICE_DOMAIN

Next.jsにおける.envファイルの取り扱いはドキュメントを参照してください。
https://nextjs.org/docs/app/building-your-application/configuring/environment-variables

5. microcms-js-sdkのインストール

公式で提供しているmicrocms-js-sdkをインストールしましょう。microcms-js-sdkはオープンソースで公開されています。
https://github.com/microcmsio/microcms-js-sdk

npm install microcms-js-sdk

次に、ルートディレクトリにlibsディレクトリとその中にmicrocms.tsを作成し、SDKの初期化を行います。

envファイルで設定した環境変数を参照するように設定します。

// libs/microcms.ts
import { createClient } from 'microcms-js-sdk';

// 環境変数にMICROCMS_SERVICE_DOMAINが設定されていない場合はエラーを投げる
if (!process.env.MICROCMS_SERVICE_DOMAIN) {
  throw new Error('MICROCMS_SERVICE_DOMAIN is required');
}

// 環境変数にMICROCMS_API_KEYが設定されていない場合はエラーを投げる
if (!process.env.MICROCMS_API_KEY) {
  throw new Error('MICROCMS_API_KEY is required');
}

// Client SDKの初期化を行う
export const client = createClient({
  serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN,
  apiKey: process.env.MICROCMS_API_KEY,
});

6. ブログ記事一覧を表示する

microCMSからデータを取得して、記事一覧を表示してみましょう。
app/page.tsxの内容を以下のように変更します。

// app/page.tsx
import Link from 'next/link';
import { client } from '../libs/microcms';

// ブログ記事の型定義
type Props = {
  id: string;
  title: string;
};

// microCMSからブログ記事を取得
async function getBlogPosts(): Promise<Props[]> {
  const data = await client.get({
    endpoint: 'blog', // 'blog'はmicroCMSのエンドポイント名
    queries: {
      fields: 'id,title',  // idとtitleを取得
      limit: 5,  // 最新の5件を取得
    },
  });
  return data.contents;
}

export default async function Home() {
  const posts = await getBlogPosts();

  return (
    <main>
      <h1>ブログ記事一覧</h1>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <Link href={`/blog/${post.id}`}> {/* 記事へのリンクを生成 */}
              {post.title} {/* タイトルを表示 */}
            </Link>
          </li>
        ))}
      </ul>
    </main>
  );
}

ブラウザで確認すると、microCMSから呼び出したコンテンツがリスト形式で表示されています。

もしエラーが表示される場合は、npm run devで起動し直してみましょう。

7. ブログ詳細記事を表示する

下図のような階層で、app/blog/[id]/page.tsxを作成します。

ブログ記事の詳細ページは記事IDに基づいて異なるページを表示するため、blogディレクトリ内にブラケット付きの[id]というディレクトリを作成します。このディレクトリは、URLパラメータを受け取るダイナミックルートを表します。

日付操作ライブラリdayjsをインストールしておきます(日付部分の表示に使用します)

npm install dayjs


// app/blog/[id]/page.tsx
import { client } from '../../../libs/microcms';
import dayjs from 'dayjs';

// ブログ記事の型定義
type Props = {
  id: string;
  title: string;
  body: string;
  publishedAt: string;
  category: { name: string };
};

// microCMSから特定の記事を取得
async function getBlogPost(id: string): Promise<Props> {
  const data = await client.get({
    endpoint: `blog/${id}`,
  });
  return data;
}

// 記事詳細ページの生成
export default async function BlogPostPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params; // IDを取得
  const post = await getBlogPost(id);

  // dayjsを使ってpublishedAtをYY.MM.DD形式に変換
  const formattedDate = dayjs(post.publishedAt).format('YY.MM.DD');

  return (
    <main>
      <h1>{post.title}</h1> {/* タイトルを表示 */}
      <div>{formattedDate}</div> {/* 日付を表示 */}
      <div>カテゴリー:{post.category && post.category.name}</div> {/* カテゴリーを表示 */}
      <div dangerouslySetInnerHTML={{ __html: post.body }} /> {/* 記事本文を表示 */}
    </main>
  );
}

// 静的パスを生成
export async function generateStaticParams() {
  const contentIds = await client.getAllContentIds({ endpoint: 'blog' });

  return contentIds.map((contentId) => ({
    id: contentId, // 各記事のIDをパラメータとして返す
  }));
}

dangerouslySetInnerHTMLはXSSのリスクを伴うため、非推奨とされています。
今回の構成では入力がmicroCMSのリッチエディタからのみ行われ、ユーザーの自由入力が含まれていないため、安全性が確保されているとして利用しています。

generateStaticParamsでブログ記事のIDを取得し、Next.jsに対して各IDに対応する静的ページを生成するように指示しています。

404ページも用意しておきましょう。appディレクトリにnot-found.tsxを作成すると、静的な404ページを作成することができます。指定されていないURLにアクセスした際に、カスタマイズされた404ページを表示することが可能です。

// app/not-found.tsx
export default function NotFound() {
  return (
    <main>
      <h1>404</h1>
      <p>ページが見つかりませんでした</p>
    </main>
  );
}

パスがないURLにアクセスすると、404ページが表示されることが確認できます。

8. CSSで外観を整える

今回は、globals.csslayout.tsxで読み込んで全体のスタイリングに適用し、個別のコンポーネントではCSS Modulesを使用してコンポーネントレベルでスタイリングを行います。

globals.css
globals.cssはプロジェクト全体に適用される共通のスタイルを定義するためのCSSファイルです。主にリセットCSSや共通のフォント設定、ベースの色設定などのスタイルを記述します。

CSS Modules
[name].module.cssという拡張子で命名し、各コンポーネントにインポートして使用します。CSSファイルのクラス名が自動的にユニークな名前に変換されるため、他のコンポーネントのスタイルと干渉しません。
以下はCSS Modulesでの簡単なスタイリング例です。

.text {
  color: red
}

以下のようにスタイルを指定します。

<p className={styles.text}>これはテキストです</p>


詳細記事の外観を整えていきます。本文(リッチエディタ)では以下のように登録しています。

以下のディレクトリにCSSファイルを追加します。
app/blog/[id]/page.module.css

// app/blog/[id]/page.module.css
.main {
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem 1rem;
}

.title {
  font-size: 2.5rem;
  font-weight: bold;
  color: #222;
  margin: 1rem 0;
  text-align: center;
  line-height: 1.2;
}

.date {
  font-size: 0.875rem;
  color: #888;
  text-align: center;
  margin-bottom: 0.5rem;
}

.category {
  font-size: 0.875rem;
  text-align: center;
  margin-bottom: 2rem;
}

.post h1 {
  font-size: 2.5rem;
  font-weight: bold;
  color: #333;
  margin: 2rem 0 1rem;
  line-height: 1.2;
  border-bottom: 2px solid #ddd;
  padding-bottom: 0.5rem;
}

.post h2 {
  font-size: 2rem;
  font-weight: bold;
  color: #444;
  margin: 1.5rem 0 0.75rem;
  line-height: 1.3;
  border-bottom: 1px solid #ddd;
  padding-bottom: 0.25rem;
}

.post p {
  font-size: 1rem;
  line-height: 1.6;
  color: #555;
  margin: 1rem 0;
}

.post ul {
  font-size: 1rem;
  line-height: 1.6;
  color: #555;
  margin: 1rem 0 1rem 1.5rem;
  padding-left: 1.5rem;
}

.post ul li {
  margin-bottom: 0.5rem;
}

記事詳細ページに以下のようにスタイルを反映していきます。

import { client } from '../../../libs/microcms';
import styles from './page.module.css'; // 追加
import dayjs from 'dayjs';

// 記事詳細ページの生成
export default async function BlogPostPage({ params }: { params: Promise<{ id: string }> }) {
  return (
    <main className={styles.main}>
      <h1 className={styles.title}>{post.title}</h1> {/* タイトルを表示 */}
      <div className={styles.date}>{formattedDate}</div> {/* 日付を表示 */}
      <div className={styles.category}>カテゴリー:{post.category && post.category.name}</div> {/* カテゴリーを表示 */}
      <div className={styles.post} dangerouslySetInnerHTML={{ __html: post.body }} /> {/* 記事本文を表示 */}
    </main>
  );
}

ブラウザで確認すると以下のように表示されます。

一気にブログらしくなりました!

9. ビルドして静的ファイルを生成してみる

一度ローカル環境でビルドし、静的ファイルを生成してみましょう。ビルドするために必要な.env.localファイルを作成します。内容は.env.development.localと全く同じなので、ファイルを複製するのがおすすめです。

以下のコマンドでビルドを行います。

npm run build


ビルドログが出力されます。/blog/[id]のような動的なページも生成できています。

10.ファイルをコミットする

GitHubで新しいリポジトリを作成します。

リポジトリの作成後、ローカル環境とGitHubを連携させるためのコマンドが表示されます。「…or push an existing repository from the command line」のコマンドをコピーして、ターミナルに入力します。

GitHubへのアップロードが完了しました。

11. ファイルをホスティングする

1. Vercelアカウントの登録

Vercelは、Next.jsの開発元が提供するホスティングサービスで、Next.jsとの相性がとても良いのが特徴です。以下のURLよりアカウントを登録してください。
https://vercel.com

2. プロジェクトのインポート

Vercelにログインしたら、次の手順でGitHubのリポジトリをVercelにインポートします。
右上の[Add New...]より[Project]を選択します。

Import Git Repositoryより今回作成したリポジトリの、[Import]ボタンをクリックします。
注:今回作成したリポジトリが見つからない場合は、Adjust GitHub App PermissionsよりGitHubへアクセスし、Repository accessで今回作成したリポジトリを選択してください。

3. 環境変数の設定、デプロイ

インポートしたプロジェクトの情報が表示されます。Environment Variablesに、envファイルに記載していたMICROCMS_API_KEYMICROCMS_SERVICE_DOMAINを登録します。2点とも追加できたら下部の[Deploy]ボタンをクリックしてください。

4. サイトを表示

デプロイが完了したらダッシュボードに移動し、右上の[Visit]ボタンよりサイトを表示してみてください。

12. Webhookの設定

現在の設定では、microCMSで記事を更新しても、VercelでデプロイしたWebサイトには自動的に反映されません。
静的サイトの場合、再度ビルドを行わなければ変更が反映されないためです。
そこで、Webhookを使って記事が更新されると自動でビルドが実行されるように設定します。

Vercelのダッシュボードで、先ほどデプロイしたプロジェクトのSettingsからGit > Deploy HooksにてWebhookを作成します。
Hookの名前はmicroCMS(任意の名前でOKです)、ブランチはデプロイに使用しているブランチ名(ここではmain)を記述してください。
Create Hook]ボタンをクリックすると、URLが発行されるので、それをコピーしてください。

microCMSの管理画面へ移動し、コンテンツ「ブログ」API設定 > WebhookにてWebhookを追加します。

追加]ボタンをクリックするとサービスの選択画面が表示されるので、「Vercel」を選択してください。

Webhookの識別名(任意の名前)と、先ほどコピーしたURLを入力します。
通知タイミングの設定は、ここではデフォルトのままとしますが、ご自由に変更していただいても問題ありません。
ページ下部の[設定]ボタンをクリックすると、Webhookが作成されます。

追加したWebhookが表示されました。

Webhookの動作確認のために、既存の記事を編集してみましょう。記事を公開してVercelのプロジェクトを見てみると、StatusBuildingになっています。ビルドが完了した後、編集したページを確認すると、以下のように更新されていました。

おわりに

microCMSとNext.js 15を組み合わせて、Jamstackなブログを作成しました。
Next.js 14からの変更点としては、paramsPromiseで受け取る形になったことかと思います。

Hobbyプランでは、あとひとつコンテンツAPIを作成可能なので、タグ機能やAboutページなどを作成してみてください。

まずは、無料で試してみましょう。

APIベースの日本製ヘッドレスCMS「microCMS」を使えば、 ものの数分でAPIの作成ができます。

microCMSを無料で始める

microCMSについてお問い合わせ

初期費用無料・14日間の無料トライアル付き。ご不明な点はお気軽にお問い合わせください。

お問い合わせ

microCMS公式アカウント

microCMSは各公式アカウントで最新情報をお届けしています。
フォローよろしくお願いします。

  • X
  • Discord
  • github

ABOUT ME

中野紘子
制作会社にてフロントエンドエンジニアの経験を経て、現在はmicroCMSでマーケターをしています。趣味はゲームと散歩です。