はじめに
Next.jsとヘッドレスCMSを使って簡単にブログサイトの構築をやって見たいと思います。
ヘッドレスCMSも様々なサービスが提供されていますが日本発のサービスであるmicroCMSで今回は行っていきます。(当たり前ですが日本語のドキュメントが充実しているので)
既に公式が導入方法をまとめていますのでそちらをベースにしながら適宜気になった箇所の補足などをしながら実践していきたいと思います。
microCMSのチュートリアルはこちら
今回のゴール
まず最初にMicroCMSの管理コンソール上でサンプルのブログを作成します。
その後、Next.jsでMicroCMSにアクセスしブログ情報を取得し表示するところまで実装します。
ブログを追加、更新するたびに自動でNext.jsに反映するようWebhookを設定します。
現場で実運用できるところまでご紹介しますので、この記事を参考にブログサイトの構築やホームページの運営が実現できれば幸いです。
microCMSとは
- 
ヘッドレスCMS 
 ヘッドレスCMSとは、バックエンド(コンテンツ管理)とフロントエンド(表示部分)を分離したCMSのことです。
 microCMSはAPIを通じてコンテンツを提供するため、ReactやNext.js、Vue.jsなど、好きなフレームワークやプラットフォームで自由にフロントエンドを構築できます。
- 
ノーコードで管理画面を構築 
 コーディング不要でコンテンツの管理画面を作成可能です。コンテンツタイプ(ブログ記事、製品情報、FAQなど)を自由にカスタマイズできます。
 フィールド(テキスト、画像、リッチテキストなど)を直感的なUIで設定できます。
- 
APIファースト設計 
 REST APIやGraphQL APIを利用して、コンテンツを簡単に取得できます。
 サーバーレスアーキテクチャやJamstackと相性が良く、モダンな開発手法に対応。
- 
スケーラブルで安心 
 サーバーのスケーリングを気にすることなく、大量のトラフィックにも対応可能。
 管理画面もセキュアで、多人数での運用も問題ありません。
- 
日本発のサービス 
 microCMSは日本発のサービスで、日本語でのサポートや日本市場向けの機能が充実しています。
システム構成
microCMSはバックエンドとしてコンテンツ管理を行います。
ブログを投稿する人はmicroCMSが提供しているブログ編集画面からブログを投稿します。
またmicroCMSはAPIを提供しておりNext.jsからはWeb APIでアクセスしブログ情報を取得しユーザーがそれを参照することとなります。
今回はVercel上でNext.jsを起動しVercelが発行するドメインを使って実際にアクセスするところまで確認していきます。
準備
microCMSのアカウント登録〜サービスの作成
まずはmicroCMSを使うためのアカウント登録と、コンテンツを作成するためのサービスの作成を事前に行います。
詳しくは公式のドキュメントをご参考ください。
こちらのドキュメントはAPIの作成についても記載がありますがAPIの作成からは弊社の記事でご紹介しますのでサービスの作成までで大丈夫です。
また今回のチュートリアルの範囲では費用は発生しませんのでご安心ください。
microCMSでAPIの作成
先ほどの手順でサービスの作成まで完了しているかと思います。
API(カテゴリー)の作成
①左メニューのコンテンツ(API)を押すとAPI作成メニューが表示されますので、「自分で決める」を選択します。
②API名とエンドポイントを入力します。
API名:カテゴリー
エンドポイント:categories
エンドポイントはNext.jsからアクセスする際のURLとなります。
③APIでどんなデータを渡すかを決めていきます。
今回はカテゴリー一覧を渡すのでリスト形式を選択します。
フィールドID:name
表示名:フィールド名
種類:テキストフィールド
以上でカテゴリーに関するAPIの作成が完了しました。
この段階ではまだカテゴリーに関するデータが1件もありません。
データ取得の際に困るので「追加」をクリックしてダミーのデータを作成しておきます。
3件ほど追加しました。
ちなみに、右上に「APIプレビュー」というボタンがありクリックすると各種言語で今回のAPIでデータを取得する方法がサンプルとして用意されています。
試しにJavaScriptで「取得」をクリックすると、レスポンスが返ってきます。
nameというkeyに今回追加した値がセットされていることがわかります。
また合計件数、オフセットなどの情報がcontentsとは別に取得できることも確認できました。
実際のデータを取得する前にレスポンスの内容が把握できるため開発が楽になります。ありがたい。。。
API(ブログ)の作成
再び、左メニューのコンテンツ(API)からAPI作成メニューが表示されますので、「自分で決める」を選択します。
①API名とエンドポイントを入力します。
API名:ブログ
エンドポイント:blogs
※microCMS公式チュートリアルとはエンドポイントが異なりますがご自身の好みで問題ありません。
②APIでどんなデータを渡すかを決めていきます。
今回はブログ一覧を渡すのでリスト形式を選択します。
bodyというフィールドIDの本文をセットする箇所ですが「リッチエディタ」という種類にしています。
リッチエディタはテキストだけでなく文字の色、インデント、引用、テーブルなどブログを見やすくするための様々な装飾が可能となります。
実態としてはHTMLをセットしてAPIで受け渡しすることとなります。
Next.jsでは受け取ったHTMLを読み込んでブログとして表示させることとなります。
詳しい変換処理については後ほどNext.jsの設定の際にご紹介します。
後は、categoryというフィールドIDを設定しており、ここにセットする情報は先ほど作成した「カテゴリー」を参照することとなります。
以上でブログに関するAPIの作成が完了しました。
こちらも、この段階ではまだブログに関するデータが1件もありませんので「追加」をクリックしてサンプルとなるブログを作ります。
こんな感じで適当にブログ記事を作っていきます。
カテゴリーを選択する欄が本文の下にありますのでこちらで先ほど作成したカテゴリーを割り当てていきましょう。
右上の「公開」をクリックすると登録されます。
「下書き保存」をクリックして登録することも可能ですがAPIでは取得できないブログ記事になりますのでご注意ください。
1件だと少ないので3件作成します。
なおブログに関してもAPIプレビューでレスポンス内容をあらかじめ確認することができます。
3つ目のブログ記事についてはbodyを見ていただくと、pタグで「テスト」と出力されています。
HTMLがレスポンスとして返ってきていることを確認できました。
またcategoryの中にnameがあってcookingがセットされておりブログを分類するためのカテゴリーがセットされていることも確認できます。
サンプルのデータも作成できたので次に進みます。
Next.jsプロジェクトの作成
ブログが準備できましたのでそれを表示するためのNext.jsを準備していきます。
コマンドラインでプロジェクトを作成します。
npx create-next-app@latest nextjs-microcms-blog-tutorial
起動できることを確認しておきます。
npm run dev
余談ですがNext.js 15からTurbopackが安定版としてリリースされており、初期起動やコード変更時のリフレッシュがこれまでより高速化されたようです。
言われてみると早くなったようなそうでもないような。。。
気になる方はこちらの記事もご参照ください。
Next.jsとmicroCMSを連携する
今回のメインテーマとなります。
どうやってmicroCMSで作成したAPIを使ってブログ情報を取得するかというお話になります。
APIキーの取得
microCMSではリクエストにAPIキーを含めることで特定のデータを取得できます。
APIは公開していると言っても誰でもAPIを使えるわけではないようにアクセス制限がかかっているということになります。
microCMSに再び戻り2つの情報を取得します。
MICROCMS_API_KEY
microCMS 管理画面の「サービス設定 > API キー」から確認することができます。
MICROCMS_SERVICE_DOMAIN
microCMS 管理画面の URL(https://example.microcms.io)の example の部分です。
私の作成したサンプルのサービスではtdipisxadbが、それに該当します。
envファイルに設定
Next.jsのプロジェクト直下に.env.development.localを作成し先ほどの値を設定します。
MICROCMS_API_KEY=xxxxxxxx
MICROCMS_SERVICE_DOMAIN=xxxxxxxx
xxxxxxxxの部分はご自身の登録したサービスに合わせて設定をお願いします。
SDKのインストール
microCMSとNext.jsの連携については公式が提供しているSDKを使って行います。
READMEにSDKの使い方が詳しく記載されていますのでリンクを貼っておきます。
早速、インストールします。
npm install microcms-js-sdk
SDKの初期化を行う共通コードの作成
先ほどインストールしたSDKを使って接続するのですが様々な場面でAPIアクセスを行うため共通処理をまとめておきます。
libs/microcms.tsファイルを作成し下記のコードを書いていきます。
// 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,
});
コードの解説をします。
// 環境変数に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,
});
環境変数を引数にしてSDKの初期化を行なっています。
このclientを使ってmicroCMSからデータを取得します。
Next.jsでブログ記事一覧を取得・表示する
Next.jsのトップページにあたる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: 'blogs', // '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 className='p-4'>
      <h1>ブログ記事一覧</h1>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <Link href={`/blog/${post.id}`}> {/* 記事へのリンクを生成 */}
              {post.title} {/* タイトルを表示 */}
            </Link>
          </li>
        ))}
      </ul>
    </main>
  );
}
getBlogPostsを使ってサーバーサイドでデータ取得を行なっております。
取得したブログ一覧にあたるpostsはmapで展開しブログのタイトルを表示させています。
またNext.jsのLinkを使用しており、タイトルをクリックすると/blog/${post.id}に遷移できるようになっています。
この遷移先はブログ詳細を表示する想定となっています。
なお、まだ遷移先のページは作成していないためタイトルをクリックすると404が表示されます。
下記のようにブログタイトルが表示されればOKです。
Next.jsからmicroCMSにアクセスしてブログ一覧を表示させることに成功しました。
Next.jsでブログ詳細を表示する
ブログ詳細を表示するため新しくページを作成します。
app/blogs/[id]/page.tsxファイルを作成し下記のコードを書いていきます。
import { client } from '../../../libs/microcms';
// ブログ記事の型定義
type Props = {
  id: string;
  title: string;
  body: string;
  publishedAt: string;
  category: { name: string };
};
// microCMSから特定の記事を取得
async function getBlogPost(id: string) {
  const data: Props = await client.get({
    endpoint: 'blogs',
    contentId: id,
  });
  return data;
}
// 記事詳細ページの生成
export default async function BlogPostPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const post = await getBlogPost(id);
  const formattedDate = post.publishedAt.slice(0, 10); // 日付を整形
  return (
    <main className='p-4'>
      <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: 'blogs' });
  return contentIds.map((contentId) => ({
    id: contentId,
  }));
}
コードの解説をしていきます。
microCMSから特定の記事を取得するためのサーバーサイドで行う処理です。
getメソッドにcontentIdをセットすることで該当のidのブログ記事のみ取得可能となります。
async function getBlogPost(id: string) {
  const data: Props = await client.get({
    endpoint: 'blogs',
    contentId: id,
  });
  return data;
}
getメソッドでブログ記事を取得するにはendpoint、contentId以外にもqueriesなどがあります。
詳しくは下記のドキュメントをご参考ください。
export default async function BlogPostPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const post = await getBlogPost(id);
  const formattedDate = post.publishedAt.slice(0, 10); // 日付を整形
  return (
    <main>
      <h1>{post.title}</h1> {/* タイトルを表示 */}
      <div>{formattedDate}</div> {/* 日付を表示 */}
      <div>カテゴリー:{post.category && post.category.name}</div> {/* カテゴリーを表示 */}
      <div dangerouslySetInnerHTML={{ __html: post.body }} /> {/* 記事本文を表示 */}
    </main>
  );
}
先ほどのブログ記事を取得する処理を使って取得した後に表示する処理となります。
対象のIDはパスに含まれた[id]を使っています。
またHTMLが含まれているbodyを表示する部分にはdangerouslySetInnerHTMLを使用しています。
dangerouslySetInnerHTML は、React において、HTML を直接 DOM に挿入するためのプロパティです。
この機能を使用すると、文字列として提供された HTML をそのまま描画できます。
通常、React は JSX を使って安全に DOM を更新しますが、このプロパティを使うとその仕組みをバイパスして、任意の HTML を挿入できます。
microCMSのリッチエディタという機能でブログ本文を作成していますが基本的にHTMLをそのまま埋め込む形ではないのでdangerouslySetInnerHTMLで表示させて問題ありません。
しかし任意のHTMLをdangerouslySetInnerHTMLで表示させる場合にはインジェクションの可能性があるため、その場合はhtml-react-parserなどのパーサーを使って、サニタイズ(不要なスクリプトを除去する処理)してから表示するのが望ましいかと思います。
export async function generateStaticParams() {
  const contentIds = await client.getAllContentIds({ endpoint: 'blogs' });
  return contentIds.map((contentId) => ({
    id: contentId,
  }));
}
generateStaticParams は、Next.js が提供する関数です。
動的ルートセグメントと組み合わせて、ビルド時に静的にルートを生成するために使用できます。
ビルド時に実行され、返されたリストを使用して静的ページを生成します。
動的に生成するページと比較してレスポンスが早くなります。
ユーザーからのリクエストでページ内容を変える必要がある検索画面やマイページ的な画面では動的生成が必要ですが、ブログサービスなのどの場合は事前に生成しておくことをお勧めします。
コードを書いた後、トップページからリンクをクリックして頂くとブログ詳細が表示されるかと思います。
microCMSから送られてきたHTMLが表示されていることが右側のElementsで確認できます。
ただ、cssが何もセットされていないため表示を見るとテキストが並んでいるだけに見えます。
例えばリストやH1タグの見出しについてはcssをつけることで見やすさが変わるかと思いますので対応したいと思います。
ブログ詳細をCSSで装飾する
Next.jsでCSSを適用する方法は幾つかあります。
デフォルトではTailwindcssが使えるようになっており、JSXを書く場合であればTailwindcssがお勧めですが、microCMSから送られてくるHTMLに対してCSSを適用するのでTailwindcssは使えません。
今回はCSS Moduleを使って送られてきたHTMLに対してCSSを適用していきます。
CSS Moduleは適用範囲を絞る(特定のコンポーネントにのみCSSを適用する)ことが可能です。
そのため例えばH1タグのフォントサイズを32pxにしようと言った時に、サイト全てのH1タグに適用されるのでなくブログ詳細を表示する箇所のH1タグにのみ反映されますので全体を考慮せずCSSを決められます。
app/blog/[id]/page.module.cssにCSSを書いていきます。
.main {
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem 1rem;
  background-color: white;
}
.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;
  color: #888;
}
.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;
  list-style-type: disc;
}
.post ul li {
  margin-bottom: 0.5rem;
}
.post table {
  width: 100%;
  border-collapse: collapse;
  margin: 20px 0;
}
.post th {
  padding: 10px;
  text-align: left;
  border: 1px solid #ddd;
  background-color: #f4f4f4;
  font-weight: bold;
}
.post td {
  padding: 10px;
  text-align: right;
  border: 1px solid #ddd;
}
CSSを作成したらそれをブログ詳細に適用します。
app/blogs/[id]/page.tsxファイルを編集します。
+ import styles from './page.module.css';
(※ 一部省略)
export default async function BlogPostPage({
  params: { id },
}: {
  params: { id: string }
}) {
  const post = await getBlogPost(id);
  const formattedDate = post.publishedAt.slice(0, 10); // 日付を整形
  return (
-     <main>
+     <main className={styles.main}>
-       <h1>{post.title}</h1> {/* タイトルを表示 */}
+       <h1 className={styles.title}>{post.title}</h1> {/* タイトルを表示 */}
-       <div>{formattedDate}</div> {/* 日付を表示 */}
+       <div className={styles.date}>{formattedDate}</div> {/* 日付を表示 */}
-       <div>カテゴリー:{post.category && post.category.name}</div> {/* カテゴリーを表示 */}
+       <div className={styles.category}>カテゴリー:{post.category && post.category.name}</div> {/* カテゴリーを表示 */}
-       <div dangerouslySetInnerHTML={{ __html: post.body }} /> {/* 記事本文を表示 */}
+       <div className={styles.post} dangerouslySetInnerHTML={{ __html: post.body }} /> {/* 記事本文を表示 */}
    </main>
  );
}
cssをインポートしてそれぞれのdivに適用します。
適用できたら再度ブログ詳細を確認します。
テーブルを作成したためテーブルのためのCSSも追加しています。
CSSについての細かい部分については省略します。
まずはCSS ModuleでmicroCMSから送られてきたHTMLを装飾できるという点をご理解頂けたらと思います、
Next.jsをビルドする
これまでは開発モードでローカル環境にて動作確認を実施していました。
VercelでNext.jsを動かすことができるか確認したいため一度ローカル環境でビルドしてエラーが発生しないか確認します。
npm run build
エラーが出てしまいました。
環境変数を読み込むファイルが.env.development.localだとビルドするときは読み込めないため、.env.localをコピーで作成します。
すると今度はビルドが通りました。
Vercelにデプロイ
Next.jsをVercelにデプロイしてアクセスできるか確認します。
VercelはNext.jsの開発元となっておりNext.jsを簡単にデプロイできる環境を用意しています。
Vercelのアカウント登録
無料となります。下記のリンクから登録できます。
GitHubとの連携
GitHubと連携することでリポジトリを選択するだけでデプロイが可能となります。
「Add New...」-> 「Project」を選択します
連携済みのGitHubから対象のリポジトリを探して「Import」をクリック
ここでは環境変数を設定します。
.env.XXXファイルで設定していた、MICROCMS_API_KEYとMICROCMS_SERVICE_DOMAINをEnviroment Variablesに設定します。
.envファイルはGitHubの連携対象外となっており、Vercelで直接設定します。
「Deploy」をクリックするとデプロイ処理が開始されます。
デプロイ中ですね。
デプロイが完了しました。
ダッシュボードに移動するとVercelが発行したドメインでデプロイされていることが確認できます。
Vercelの払い出したドメインでアクセスしてみます。
トップページがローカル環境で確認した内容と同じものが表示されています。
ページ遷移も問題なくブログ記事詳細も表示されています。
Webhookの設定
今回はブログ記事詳細を事前に生成する(ビルド時に生成)とお伝えしました。
その場合、新しい記事が作成されてもビルドを行うまでブログ記事が表示されません。
Next.jsのビルドはGitHubでコードがmainブランチに反映された際にVercelが自動で行う機能が備わっていて、開発からデプロイはスムーズに行えます。
しかしブログ記事の作成では自動でビルドは行われません。
そこでWebhookを使ってmicroCMSからブログ記事の更新をVercelに通知しビルドを動かす設定をします。
そうすることでブログの更新がそのままブログの表示まで繋がるようになります。
Vercelでデプロイを動かすためのAPIを作成
「Setting」-> 「Git」でDeploy Hooksという項目がありますので画像のように入力し「Create Hook」をクリックすると、APIのURLが発行されます。
microCMSでWebhookの登録
コンテンツ「ブログ」の「API設定」 -> 「Webhook」にてWebhookを追加します。
Vercelで取得したAPIのURLを登録します。
通知タイミングの設定も可能でコンテンツの公開以外にも並び替えやあるいはコンテンツの公開終了などでもデプロイを動かすことは可能です。
以上でWebhookの設定は終了です。
実際に新しいブログ記事が表示されるかテストします。
適当にブログ記事を作成します。
ビルドには1分程度かかりますので少し待ちます。
再度、トップページを確認すると新しいブログ記事のタイトルが表示されました。
リンクをクリックするとブログ記事詳細も確認できました。
さいごに
今回の記事を通してmicroCMSとNext.jsで簡単にブログサイトが構築できるイメージができましたでしょうか。
ブログ記事は貴重な知的財産になりますので安定したSaasサービス(今回であればmicroCMS)で保管し、表示部分についてはNext.jsとCSSを活用して自分なりにカスタマイズしながら提供できます。
Vercelを使うことで素早く公開し自分の技術ブログとして様々な人に見せることも可能です。
会社で運営しているブログサイトが見栄えが悪い、パフォーマンスが遅いなど問題が出ているようでしたら切替のきっかけになれば幸いです。
また、ご自身の技術ブログを造られたり、これから作ろうということでしたら構成の一案として見ていただけたら幸いです。





































