ヘッドレスCMSガイド

ヘッドレスCMSがよくわかる情報発信サイト

2025-05-27

React×ヘッドレスCMSの課題 ~Next.jsによる解決策~

React×ヘッドレスCMSの課題 ~Next.jsによる解決策~
Webサイト制作において、検索エンジン最適化(SEO)やデザインの自由度を両立したサイト構築が強く求められており、その解決方法としてヘッドレスCMSとReactを組み合わせた手法が注目されています。 しかし、React単体では初期表示速度やSEO対策に課題があるため、Next.jsを利用したサイト構築がReactの上位互換として採用されることも増えてきました。 本記事ではReactの特長や課題を紹介しつつ、 ・ヘッドレスCMSは、従来のCMSと何が違うのか? ・ヘッドレスCMSを導入することで具体的にどのような効果が期待できるのか? ・ヘッドレスCMSでサイトを作るのにReactではなくNext.jsを採用するメリットは何か? といったフロント技術選定における悩みを解消する方法について解説し、さらにはNext.jsを用いたより効果的なWebサイトの公開手順についてもご紹介します。
記事入稿も、公開予約も、AIで。
人とAIの理想的な協業体制を実現するヘッドレスCMS「NILTO」

コンテンツ運用の「時間不足」「アイデア枯渇」「人手不足」をAIが解決。

フェンリルの国産ヘッドレスCMS『NILTO』新機能「NILTO MCP」はAIエージェントと協業して情報収集から入稿・承認・公開予約まで自動化し、リードタイムとコストを削減。

現在全プランでテクニカルプレビュー提供中。
今ならあなたのフィードバックがNILTOに反映されるチャンスです。

ヘッドレスCMSとは

そもそも、CMS(コンテンツ管理システム)は、Webサイトやアプリで提供される様々な情報(文章、画像、動画など)を効率的に管理するための仕組みです。

「ヘッドレスCMS」は、その中でも、コンテンツを表示する部分(フロントエンド)と、コンテンツを管理する部分(バックエンド)が分離された構造を持つCMSを指します。

従来のCMSとの違い

従来のCMSでは、コンテンツ(記事や画像など)の管理と、それらをWebページとして表示する機能がセットになっており、「コンテンツを入れる箱」と「Webページのデザイン」が一体になっているイメージでした。

一方でヘッドレスCMSは、「ヘッド」(見た目の部分)を持たないため、コンテンツの管理と表示を独立して行うことができます。

ヘッドレスCMSのメリット

ヘッドレスCMSは従来のCMSの課題を解決し、様々なメリットをもたらします。具体的に見ていきましょう。

フロントエンドの自由度

従来のCMSではサービス固有のテンプレート(ひな形)を使う必要があるため、デザインの自由度が制限されることが多々あります。

一方で、ヘッドレスCMSはReactVue.jsなどの最新のJavaScriptフレームワークを用いることが可能であり、開発者がデザインや機能を自由に実装できます。

多様なデバイスへの対応

従来のCMSではWebサイトでの表示を前提としている場合がほとんどであり、スマホアプリなどへの対応が難しいケースも少なくありません。

ヘッドレスCMSならWebサイトだけでなく、モバイルアプリIoTデバイスなど多様なデバイスに向けた展開が可能です。コンテンツの配信先を限定しない、柔軟な配信システムは、デジタル技術の発展した現代にぴったりです。

表示速度の改善(SEO対策)

スマートフォンからのアクセスが主流となっている現在、Webサイトの表示速度は、ユーザーエクスペリエンスの向上だけでなく、SEO対策においても非常に重要な要素です。

ヘッドレスCMSは、Webページに必要なデータのみをAPI経由で取得するため、従来のCMSよりも高速なWebページ表示を実現できます。

ヘッドレスCMSにおけるフロントエンド技術の選定

現代のWebサイト制作は、目まぐるしいスピードで進化を続けています。かつてはテキストと画像が表示されるだけでしたが、ユーザーの操作に応じてリアルタイムに情報が更新されるものが一般的になっています。

ヘッドレスCMSは、このような現代的なWebサイト制作でよく用いられており、ReactNext.jsなどのモダンなフロントエンド技術との相性が良いです。

Reactの特徴

Reactは、UI構築に特化したJavaScriptライブラリです。
コンポーネントベースの開発により、効率的なUI構築が可能です。ネイティブアプリのようなリッチなUIを構築でき、スムーズなアニメーションやインタラクションにより、ユーザー体験が向上します。

さらに、Reactは可読性が高く、コードの保守性にも優れており、学習リソースが豊富であるため、比較的初学者にも優しいという特徴があります。

Reactを採用する際の課題

Reactは、UI構築に特化したJavaScriptライブラリである点において、ヘッドレスCMSとの連携に適しています。
しかし、Reactはクライアントサイドレンダリング(CSR)が基本となるため、初期表示速度やSEO対策に課題が残る場合があります。

CSRでは、Webページの描画をブラウザで行うため、JavaScriptの実行が完了するまでコンテンツが表示されず、ユーザーに空白のページが表示される時間が長くなることがあります。検索エンジンのクローラーは、JavaScriptの実行によって生成されたコンテンツを認識しにくい場合があります。

さらに、大規模なアプリケーションでは、パフォーマンスの最適化が重要になります。特に、多数のコンポーネントを使用する場合、レンダリングの最適化を行わないと、ページの動作が遅くなることがあります。

ReactよりもNext.jsがおすすめな理由

Next.jsは、Reactをベースにしたフレームワークであり、サーバーサイドレンダリング(SSR)静的サイト生成(SSG)などの機能を提供し、ReactアプリケーションのパフォーマンスとSEOを向上させます。

これにより、Reactで課題となりやすい初期表示速度とSEOに対応し、よりパフォーマンスの高いWebサイトを制作できます。

レンダリング手法の選択

レンダリングとは、WebブラウザがHTML、CSS、JavaScriptなどのコードを解釈し、Webページを画面に表示するプロセスのことです。Next.jsでは、レンダリング処理をいつ、どこで行うかによって3つの手法を選択できます。

  • CSR(Client-Side Rendering)
    クライアント側でページを生成する手法。Reactの基本的なレンダリング手法です。
  • SSR(Server-Side Rendering)
    サーバー側でページを生成し、クライアントに送信する手法。常に最新の情報を表示するサイトに適しています。
  • SSG(Static Site Generation)
    ビルド時にページを生成する手法。静的なコンテンツが多いサイトに適しており、高速な表示が可能です。

パフォーマンス向上

Next.jsは、SSRSSGなどのレンダリング手法により、ページの初期表示速度を大幅に向上させることができます。これにより、ユーザーはWebサイトをストレスなく閲覧できます。

SEOに強いサイトを作りやすい

SSRにより、検索エンジンのクローラーがコンテンツを認識しやすい形でページを生成できます。読み込み速度が速いWebサイトは、検索エンジンの評価も高くなる傾向があり、SEO対策にも貢献します。

比較結果

Webサイト制作におけるフロントエンド技術選定で重要なポイントは、サイトのパフォーマンスやSEOの対策であり、その観点ではNext.jsが優れていると言えます。

そのため、本記事ではReactではなくNext.jsを採用して、Webサイト構築手順を解説します。

サイト公開までの手順

今回は、以下のようなブログトップページと、ブログ詳細ページを作成します。

ブログトップページのキャプチャブログ記事詳細ページのキャプチャ

Next.js のセットアップ

Next.js のインストールは、自動インストールで行うのがおすすめです。手動インストールでも可能ですが、特別な理由がない限り自動で問題ありません。

インストール方法の違いについては、Next.js の公式ドキュメントをご確認ください。
https://nextjs.org/docs/app/getting-started/installation

自動インストール手順

ターミナルで以下のコマンドを実行します。

npx create-next-app@latest

実行すると質問が表示されるので、対話形式で初期設定を進めます。

What is your project named? my-app
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like your code inside a `src/` directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to use Turbopack for `next dev`?  No / Yes
Would you like to customize the import alias (`@/*` by default)? No / Yes
What import alias would you like configured? @/*

今回は以下の内容で設定しています。ルーティングはNext.js が推奨する App Router を選択しました。

プロジェクト名 - test-headless-cms
TypeScriptの利用 - Yes
ESlintの利用 - Yes
TailWind CSSの利用 - Yes
srcディレクトリを使うか - Yes
App routerを使うか - Yes
インポートエイリアスをカスタマイズするか - No

App Router 設定は重要なポイントになります。のちほど詳しく解説します。

ディレクトリ構成

インストール時にApp Routerを選択したので、ディレクトリ構成は以下の通りになっています。

test-headless-cms
└── src
    └── app
        ├── blog
        │   └── [slug]
        │       └── page.tsx // ブログ記事詳細ページ
        └── page.tsx          // ブログトップページ

App Rouer とは

Pages Routerの代替として提供される新しいルーティングシステムです。App Routerは、より柔軟なルーティング、React Server Components(RSC)の採用、そして、大規模なアプリケーションでより優れたパフォーマンスと可観測性を提供します。

ディレクトリ構成は、URLに対応するページ用のコンポーネントを配置する必要があります。


※ ちなみに、 Pages Router を選択した場合は以下のような構成になります。

test-headless-cms
└── pages
    ├── blog
    │   └── [slug].tsx   // ブログ記事詳細ページ
    └── blog.tsx          // ブログトップページ

ヘッドレスCMSのセットアップ

Next.js のセットアップの次は、ヘッドレスCMSのセットアップを進めます。
カードの登録不要ですぐに利用開始でき、無料プランでもサイトを複数作成できるので、今回はNILTOを選択しました。

アカウント登録

NILTO プロダクトサイトにアクセスし、「無料ではじめる」ボタンから進めます。
https://nilto.com/ja

NILTOアカウント登録方法

モデル作成

ブログモデルを作成します。追加したフィールドは以下の通りです。

  • 記事タイトル(1行テキストフィールド)
  • サムネイル(メディアフィールド)
  • 記事本文(フレキシブルテキストフィールド)
  • スラッグ(1行テキストフィールド)
モデル作成画面

コンテンツ作成

ブログ記事の元となるコンテンツを登録します。画像のように、必要な情報(記事本文など)を入力します。
記事が完成したら、右上のボタンで公開します。

コンテンツ作成画面

フロントエンド開発

APIキーの取得

Webサイト側から、NILTOで生成されたAPIキーをリクエストヘッダーに付与してリクエストすることで、コンテンツを取得することができます。

NILTOで公開したコンテンツを表示させたいので、「公開のみ」のAPIキーのトークンをコピーします。

公開用APIキー

リクエストヘッダーの指定形式については、リンク先の記事を参照ください。
https://nilto.com/ja/docs/topics/tutorial#38def47dedb74ac0f69eabacdfd2bd1d0cb3afae

APIキーの設定

環境変数として、NILTOのAPIキーを.env ファイルに定義します

// .env
NILTO_API_KEY=XXXXXXXXXXXXX

型定義

NILTOで作成したモデルの設定に沿って、型定義ファイルに定義を記述します。

// src/types/contents.ts

export interface ApiResponse {
    data: Content[];
}

export interface Content {
    _id: number	      	// コンテンツのID
    _model: string  	// モデルのLUID
    _status: string   	// 公開状態
    _title: string	
    slug: string
    title: string          // コンテンツのタイトル
    article: string        // コンテンツの本文
    _created_at: Date	// コンテンツ作成日
    _updated_at: Date	// コンテンツ更新日
    _published_at: Date	// コンテンツ公開日
    thumbnail: {    	// 画像
        url: string;
        alt: string;
    }; 
}

モデルの設定内容と差異があると、表示ができないので注意が必要です。
例)記事本文は、articleというLUIDで作成されている必要があります

モデルのLUIDについての詳細は、チュートリアルのモデル作成の項目をご確認ください。
https://nilto.com/ja/docs/topics/tutorial#90e8e45a02f319f9470de12f8668e15cf5f7a37b

コンテンツの取得

ブログトップページとブログ記事詳細ページ用のページコンポーネントを作成し、コンテンツの取得処理を記述します。

今回はNext.jsプロジェクトのセットアップ時に App Router を選択しているため、SSRに相当する機能を利用したい場合は fetch メソッドのcacheオプションをno-storeで指定する必要があります。

// src/app/page.tsx(ブログトップページ)

import Link from 'next/link';
import type { ApiResponse } from '@/types/contents';


export default async function BlogToplPage() {
  const apiKey = process.env.NILTO_API_KEY;
  const requestHeader = new Headers();

  requestHeader.set('X-Nilto-Api-Key', apiKey ?? ''); 
  requestHeader.set('Content-Type', 'application/json');

  const apiUrl = 'https://cms-api.nilto.com/v1/contents?model=blog&order=-_updated_at';

  const response = await fetch(apiUrl, {
    method: 'GET',
    headers: requestHeader,
    cache: 'no-store', // 常に最新データを取得
  });

  const apiResult = await response.json() as ApiResponse;
  const contents = apiResult?.data || [];

  return (
    <div className='top-page'>
      <div>
        <h1>技術ブログ</h1>
        <ul style={{ listStyle: 'none', padding: 0 }}>
          {contents.map((content) => {
            return (
              <li key={content._id || content._title} style={{ marginBottom: '10px' }}>
                <Link href={`/blog/${content.slug}`} style={{ textDecoration: 'underline' }}>
                  {content._title}
                </Link>
                {<span style={{ marginLeft: '10px', color: '#888' }}>
                  {new Date(content._published_at).toLocaleDateString('ja-JP', {
                    year: 'numeric',
                    month: '2-digit',
                    day: '2-digit',
                  })}
                </span>}
              </li>
            );
          })}
        </ul>
      </div>
    </div>
  );
}
// src/app/blog/[slug]/page.tsx(ブログ記事詳細ページ)

import Link from 'next/link';
import Image from 'next/image';
import type { ApiResponse } from '@/types/contents';


type Props = {
  params: Promise<{ slug: string }>;
};

export default async function BlogDetailPage({ params }: Props) {
  const slug = (await params).slug;

  const apiKey = process.env.NILTO_API_KEY;
  const requestHeader = new Headers();

  requestHeader.set('X-Nilto-Api-Key', apiKey ?? ''); 
  requestHeader.set('Content-Type', 'application/json');

  const identifier = decodeURIComponent(slug);
  const apiUrl = `https://cms-api.nilto.com/v1/contents?model=blog&slug[eq]=${encodeURIComponent(identifier)}`;

  const response = await fetch(apiUrl, {
    method: 'GET',
    headers: requestHeader,
    cache: 'no-store', // 常に最新データを取得
  });

  const apiResult = await response.json() as ApiResponse;
  const content = apiResult.data[0];

  return (
    <article>
      <div>
        <h1>{content._title}</h1>
        <div className='my-4 relative' style={{ width: '100%', paddingTop: '56.25%' }}> 
          <Image
            src={content.thumbnail.url}
            alt={content.thumbnail.alt || content._title || '記事サムネイル'}
            fill
            style={{ objectFit: 'contain' }}
            priority
          />
        </div>

        <p style={{ color: '#666', fontSize: '0.9em' }}>
          公開日: {new Date(content._published_at).toLocaleDateString('ja-JP', { year: 'numeric', month: '2-digit', day: '2-digit' })}
        </p>
        <div dangerouslySetInnerHTML={{ __html: content.article }} />
      </div>
      <div style={{ marginBottom: '20px', marginTop: '10px' }}>
        <Link
          href='/'
          style={{ textDecoration: 'underline', color: 'blue' }}
        >
          戻る
        </Link>
      </div>
    </article>
  );
}

コンテンツAPIの詳細については、NILTOのAPIリファレンスをご覧ください。
https://nilto.com/api

サイトの公開

サイトを公開するために、リモート環境(サーバー)にデプロイします。

GitHub

デプロイしたいサイトのプロジェクトのソースコードを置くリポジトリを作成します。

ホスティングサービスの連携

今回は、ホスティングサービスとしてAWS Amplifyを利用します。
AWSのアカウントを作成し、AWS Amplifyのアプリを作成して、GitHubリポジトリと連携します。

GitHubでブランチにソースコードがプッシュされた際に、最新の状態をサイトに反映することが可能です。

1. ソースコードプロバイダーを選択

今回はGitHubを選択し、「次へ」をクリックします。

AWS Amplifyの設定画面(1)
2. リポジトリとブランチを追加

mainブランチを選択し、「次へ」をクリックします。

AWS Amplifyの設定画面(2)
3. アプリケーションの設定

アプリケーションの名前を入力します。
フレームワークはリポジトリ内のソースコードから自動で検出されるので、ビルドコマンド等は記入する必要はありません。

AWS Amplifyの設定画面(4)
4. 確認

最後に設定内容が表示されます。内容が正しいか確認し、「保存してデプロイ」をクリックします。

AWS Amplifyの設定画面(4)
5. 完成

アプリが作成されたら、完了です。

AWS Amplifyの設定画面(5)

環境変数の設定

Next.jsのサーバーコンポーネントでキャッシュせずにリクエストごとに最新データを取得したい場合は、
Amplifyの環境変数の設定画面ではなく、ビルド時にechoコマンド等で変数を割り当てる必要があります。

例)build の commands 以下に echo 'NILTO_API_KEY=$NILTO_API_KEY' >> .env を追記します。

version: 1
frontend:
  phases:
    preBuild:
      commands:
        - npm ci 
    build:
      commands:
        - echo 'NILTO_API_KEY=$NILTO_API_KEY' >> .env
        - npm run build
  artifacts:
    baseDirectory: .next
    files:
      - '**/*'
  cache:
    paths:
      - .next/cache/**/*
      - .npm/**/*

詳細はAWS公式ドキュメントをご確認ください。
https://docs.aws.amazon.com/en_us/amplify/latest/userguide/ssr-environment-variables.html

まとめ

本記事では、ヘッドレスCMSとNext.jsを組み合わせたウェブサイト構築の手順について解説しました。

この構成は、コンテンツ管理の柔軟性と、フロントエンドにおけるパフォーマンスおよびSEOの最適化を目指す場合に有効な選択肢です。

より高速で、柔軟なウェブサイト開発手法として、ぜひヘッドレスCMSとNext.jsを活用したウェブサイト構築に挑戦してみてください。

記事入稿も、公開予約も、AIで。
人が行う作業にサヨナラできるヘッドレスCMS「NILTO」

コンテンツ運用における 「時間がない」「アイデアが枯渇した」「人手が足りない」 といったお悩みはありませんか?

これからのコンテンツ管理は、AIエージェントがサポートする時代です。

フェンリルが提供する国産ヘッドレスCMS『NILTO』の新機能 「NILTO MCP(Model-Context-Protocol)」は、 AIエージェントによる高度なコンテンツ運用の自動化を可能にします。

プロンプトの指示で情報収集から原稿入稿、承認依頼、公開予約までを一気通貫で対応し、 施策にかかるリードタイムと作業コストを劇的に削減します。

現在は、テクニカルプレビューとして全プランでご利用いただけます。

皆様が利用されたフィードバックを元にブラッシュアップを行っていますので、 今ご利用いただければ、あなたの貴重なご意見がNILTOに反映されるチャンスです。
このタイミングでぜひご利用ください。

フェンリル株式会社

ヘッドレスCMS「NILTO」