microCMS

microCMSにおけるプレビュー機能の設計パターンについて

高宮 竜太

microCMSのようなヘッドレスCMSは見た目を持たないため、従来型のCMSとはプレビューの仕組みが根本的に異なります。
プレビューを実施するためには、フロントエンド側でCMSから下書き状態のコンテンツを取得し、レンダリングする必要があります。

ですが、実装方法についてはフレームワークやレンダリング方式、インフラ構成によって多様な選択肢があります。
本記事では、microCMSを用いたプレビュー機能の実装について、主要な設計パターンとその特徴を解説します。

microCMSにおけるプレビュー機能の設計パターン

microCMSでのプレビュー機能の実装は、以下のようなパターンが考えられます。

  1. 本番環境のフロントエンドを使用してプレビューを実現する方法
  2. プレビュー専用のフロントエンド環境を用意する方法
  3. microCMSの複数環境を活用する方法

注意点

  • 本記事では、プレビューの仕組みについては解説していません。プレビューの仕組みについては、ドキュメント「画面プレビュー」をご参照ください。
  • 実装については「draftKeyを利用して実装する方法」もしくは「下書きコンテンツの全取得が付与されたAPIキーに切り替えて実装する方法」を紹介しています。Hobbyプランの場合、APIキーが1つしか利用できないため、APIキーを切り替える方法は実装できません。そのため、「draftKeyを利用して実装する方法」を利用することを推奨いたします。
  • 実装例ではNext.jsを用いたコードを記載しています。一部、Next.jsでのみ実現可能な方法を記載していますが、極力Next.jsに依存しないような設計パターンを紹介しています。利用しているフレームワークに合わせて変更していただけると幸いです。


それでは、それぞれの実装パターンについて、詳細を見ていきましょう。

1. 本番環境のフロントエンドを使用してプレビューを実現する方法

このパターンでは、本番環境のフロントエンドをそのまま利用してプレビューを表示します。

具体的な実装方法としては、以下のレンダリング方式が考えられます。

  1. クライアントサイドレンダリング(CSR)を利用
  2. サーバーサイドレンダリング(SSR)を利用
  3. Next.jsのDraft Modeを利用

1. クライアントサイドレンダリング(CSR)を利用

この設計では、クライアントサイドレンダリング(CSR)を利用してプレビューを実装します。
SPAのアプリケーションや、静的生成したHTMLをS3などのストレージにアップロードようなケースでこの設計が適していると考えています。多くのインフラ構成でも利用できる柔軟性があるため、規模に関わらず幅広く対応可能です。

実装例は以下の通りです。

// articles/preview/page.tsx
'use client'

import Article from '@/app/_components/article'
import { createClient } from 'microcms-js-sdk'
import { useSearchParams } from 'next/navigation'
import { Suspense, use } from 'react'

const getArticle = async (contentId: string, draftKey: string) => {
  const data = await client
    .getListDetail({
      endpoint: 'blogs',
      contentId,
      queries: { draftKey },
    })
    .catch(() => {
      return null
    })
  return data
}

const PreviewArticle = () => {
  const searchParams = useSearchParams()
  const contentId = searchParams.get('contentId')
  const draftKey = searchParams.get('draftKey')

  if (!contentId || !draftKey) {
    return <div>必要なパラメータが指定されていません。</div>
  }

  const article = use(getArticle(contentId, draftKey))

  return <Article data={article} />
}

export default function Page() {
  return (
    <Suspense fallback={<div>読み込み中...</div>}>
      <PreviewArticle />
    </Suspense>
  )
}

プレビュー用のURL(/preview)を作成して実装します。クエリパラメータから取得したコンテンツIDとdraftKeyを利用して、ブラウザからmicroCMSにリクエストを行い、受け取った下書きデータをレンダリングしています。

特長

この実装の特長は以下の通りです。

  • シンプルな実装方法
  • あらゆるホスティング環境で利用可能

注意点

注意点としては2点あげられます。

  • APIキーがクライアントサイドで露出する
  • draftKeyが漏洩した場合、誰でも下書きコンテンツを閲覧できる


APIキーがクライアントサイドで露出することは必ずしも問題になるわけではありません。APIキーの取り扱いについてはヘルプ記事の「APIキーを隠す必要はありますか?」を参照してください。

また、draftKeyが漏洩することによる、下書きコンテンツの閲覧を防ぎたい場合は、プレビューページに認証をかける等の対応が必要になります。

関連ブログ

以下のブログでもクライアントサイドレンダリングでの実装方法を紹介しております。利用しているフレームワークに合わせて参考にしてください。

2. サーバーサイドレンダリング(SSR)を利用

この設計では、サーバーサイドレンダリング(SSR)を利用してプレビューを実装します。
フレームワークホスティングサービスでSSRがサポートされている場合に適しています。例えば、フレームワークはNext.jsやRemix、ホスティングサービスにはVercelやAWS Amplify、Cloudflare Pagesなどを利用するケースが挙げられます。
また、サーバー内でmicroCMSにリクエストするため、APIキーが公開されません。APIキーを隠す必要がある場合は、この方法が適しています。

実装例は以下の通りです。

// articles/[id]/page.tsx

type Params = Promise<{ id: string }>;
type SearchParams = Promise<{ draftKey: string | undefined; contentId: string }>;

export default async function Page(props: {
  params: Params;
  searchParams: SearchParams;
}) {
  const params = await props.params;
  const searchParams = await props.searchParams;
  const contentId = params.id;
  const { draftKey } = searchParams;

  const article = await client
    .getListDetail({
      endpoint: 'blogs',
      contentId,
      queries: { draftKey },
    })
    .catch(() => {
      return null;
    });

  if (!article) {
    if (draftKey) {
      return <div>ドラフトキーが無効です。正しいキーを使用してください。</div>;
    }
    notFound();
  }

  return <Article data={article} />;
}

今回は、公開URL(/articles/:id)でプレビューも実装します。クエリパラメータにdraftKeyが含まれる場合には下書きを取得し、そうでない場合は公開データを取得してレンダリングしています。

特長

この実装の特長は以下の通りです。

  • サーバーサイドからリクエストするため、APIキーを隠蔽できる
  • 同一URLでのプレビュー表示ができる

注意点

注意点としては、CDNやフレームワークによってはプレビュー時に取得したデータを意図せずキャッシュしてしまうケースがあります。

利用しているフレームワークによっては公開コンテンツは静的ページのまま、プレビューは動的ページで確認したいケースもあるかと思います。その場合はCSRで実装するケースと同様に、プレビュー用URL(/articles/preview)からmicroCMSにリクエストをして下書きデータを表示してください。

// articles/preview/page.tsx
import Article from '@/app/_components/article'
import { client } from '@/app/_libs/microcms'

type SearchParams = Promise<{ draftKey: string | undefined; contentId: string }>

export default async function Page(props: {
  searchParams: Promise<SearchParams>
}) {
  const searchParams = await props.searchParams
  const { draftKey, contentId } = searchParams

  if (!contentId || !draftKey) {
    return <div>必要なパラメータが指定されていません。</div>
  }

  const article = await client
    .getListDetail({
      endpoint: 'blogs',
      contentId,
      queries: { draftKey },
    })
    .catch(() => {
      return null
    })

  if (!article)
    return <div>コンテンツIDかドラフトキーに無効な値が指定されています。</div>

  return <Article data={article} />
}

3. Next.jsのDraft Modeを利用

利用するフレームワークがNext.jsで、Vercelにホスティングする場合は、Next.jsのDraft Modeでプレビューを実装することを推奨しています。
SSRがサポートされているホスティングサービスであればVercelに限らず利用可能です。しかし、Cookieの取り扱いについてはホスティングサービスによって挙動が異なるため、利用するホスティングサービスの仕様を確認してください。

基本的にはNext.jsの公式ドキュメントに記載されている方法に準拠して実装します。
app/api配下の任意のパスでルートハンドラを作成します。(例: app/api/draft/route.ts)
microCMSからこのURLにアクセスをすると、シークレット値やコンテンツIDの検証などを経て、Draft Modeが有効になります。

// app/api/draft/route.ts
import { client } from '@/app/_libs/microcms'
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'

export async function GET(request: Request) {
    const { searchParams } = new URL(request.url)
    const secret = searchParams.get('secret')
    const contentId = searchParams.get('contentId')
    const draftKey = searchParams.get('draftKey')

    if (secret !== 'MY_SECRET_TOKEN' || !contentId) {
        return new Response('Invalid token', { status: 401 })
    }

    const article = await client
        .getListDetail({
            endpoint: 'blogs',
            contentId,
            queries: { draftKey: draftKey || undefined },
        })
        .catch(() => null)

    if (!article) {
        return new Response('Invalid article', { status: 401 })
    }

    const draft = await draftMode()
    draft.enable()

    redirect(`/articles/${article.id}`)
}

プレビューを表示するページでは、Draft Modeが有効かどうかを確認し、有効であれば下書きコンテンツの全取得が付与されたAPIキーを利用してコンテンツを取得します。

// articles/[id]/page.tsx
import Article from '@/app/_components/article'
import { notFound } from 'next/navigation'
import { draftMode } from 'next/headers'
import Link from 'next/link'
import { createClient } from 'microcms-js-sdk'

const draftClient = createClient({
    serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN!,
    apiKey: process.env.MICROCMS_API_KEY! /* 下書きコンテンツの全取得が付与されたAPIキー */,
})
const publishClient = createClient({
    serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN!,
    apiKey: process.env.MICROCMS_API_KEY! /* 公開済みコンテンツのみを取得できるAPIキー */,
})

export async function generateStaticParams() {
    const { contents } = await publishClient.getList({ endpoint: 'blogs' })

    return contents.map(content => ({
        id: content.id,
    }))
}

type Params = Promise<{ id: string }>
export default async function Page(props: {
    params: Params
}) {

    const { isEnabled } = await draftMode()

    const client = isEnabled ? draftClient : publishClient

    const params = await props.params
    const contentId = params.id

    const article = await client
        .getListDetail({
            endpoint: 'blogs',
            contentId,
        })
        .catch(() => {
            return null
        })

    if (!article) notFound()

    return <Article data={article} />
}

特長

この実装の特長は以下の通りです。

  • サーバーサイドからリクエストするため、APIキーを隠蔽できる
  • 同一URLで実装でき、公開コンテンツを確認する際はSSG、プレビュー確認時はSSRのようにレンダリング方式を分けられる

注意点

Cookieを利用して制御しているため、下書きコンテンツを閲覧しているのか公開コンテンツを閲覧しているかが、わかりにくい点が挙げられます。対処法としては下書き中の場合にのみ特定のコンポーネントを出力するような処理を加えることを推奨します。

関連ブログ

Draft Modeではありませんが、Pages Routerで利用できるPreview Modeを用いた方法を記載していますので参考にしてください。
Next.jsのPreview Mode+Vercelでプレビュー機能を実現する

2. プレビュー専用のフロントエンド環境を用意する方法

このパターンでは、本番環境のフロントエンドは利用せず、プレビュー用のフロントエンド環境を利用してプレビューを表示します。

フロントエンドの環境を分けたとしても、CSRやSSR、Draft Modeで実装が可能ですが、今回は同一URLで実装するため、SSRで実装する方法のみを説明します。
実装例は以下の通りです。

// articles/[id]/page.tsx

const client = createClient({
  serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN,
  apiKey: process.env.MICROCMS_API_KEY, // ホスティングサービス側の設定で環境ごとにAPIキーを切り替える
})

type Params = Promise<{ id: string }>
export default async function Page(props: {
  params: Params
}) {
  const params = await props.params
  const contentId = params.id

  const article = await client
    .getListDetail({
      endpoint: 'blogs',
      contentId,
    })
    .catch(() => {
      return null
    })

  if (!article) notFound()

  return <Article data={article} />
}

この実装では、APIキーをホスティングサービス側で切り替えるため、実装側での条件分岐やディレクトリを分ける必要はありません。

const client = createClient({
  serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN,
  apiKey: process.env.MICROCMS_API_KEY, // ホスティングサービス側の設定で環境ごとにAPIキーを切り替える
})

特長

この実装の特長は以下の通りです。

  • サーバーサイドからリクエストするため、APIキーを隠蔽できる
  • 同一URLでのプレビュー表示ができる

注意点

  • フロントエンドの環境を2環境用意する必要がある
  • プレビュー用環境には認証やアクセス制御が必要になる

3. microCMSの複数環境を活用する方法

microCMSの複数環境を活用してプレビュー機能を実装する方法を説明します。
フロントエンドだけでなく、microCMSも本番環境とは別の環境を利用することで、本番環境には影響を与えずにプレビューを確認できます。

実装例は以下のとおりです。

// articles/[id]/page.tsx

const client = createClient({
  serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN, // ホスティングサービス側の設定で環境ごとにサービスドメインを切り替える
  apiKey: process.env.MICROCMS_API_KEY, // ホスティングサービス側の設定で環境ごとにAPIキーを切り替える
})

type Params = Promise<{ id: string }>
export default async function Page(props: {
  params: Params
}) {
  const params = await props.params
  const contentId = params.id

  const article = await client
    .getListDetail({
      endpoint: 'blogs',
      contentId,
    })
    .catch(() => {
      return null
    })

  if (!article) notFound()

  return <Article data={article} />
}

この実装では、APIキーとサービスドメインをホスティングサービス側で切り替えているため、実装側での条件分岐やディレクトリを分ける必要はありません。

const client = createClient({
  serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN, // ホスティングサービス側の設定で環境ごとにサービスドメインを切り替える
  apiKey: process.env.MICROCMS_API_KEY, // ホスティングサービス側の設定で環境ごとにAPIキーを切り替える
})

特長

この実装の特長は以下の通りです。

  • 本番環境のフロントエンドとmicroCMSに干渉させずにプレビューを確認できる
  • 本番環境のAPIキーを隠蔽できる
  • 同一URLでのプレビュー表示ができる

注意点

  • 複数環境はBusinessプランから利用可能
  • 複数環境から本番環境へのコンテンツ移行は、API経由や手作業で行う必要がある
  • フロントエンドの環境を2環境用意する必要がある
  • プレビュー用環境には認証やアクセス制御が必要になる

さいごに

microCMSでプレビュー機能を実装するさまざまな方法について、例を交えながらご紹介しました。それぞれの方法には利点や適用しやすいケースがあり、採用する技術スタックやホスティングサービスによって最適なアプローチが変わります。

どの方法を選択するかは、プロジェクトの規模、チームのスキルセット、要件、そして運用体制など、さまざまな要因によって変わります。ぜひこの記事を参考に自身のプロジェクトに最適な方法を見つけてみてください。

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

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

microCMSを無料で始める

microCMSについてお問い合わせ

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

お問い合わせ

microCMS公式アカウント

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

  • X
  • Discord
  • github

ABOUT ME

高宮 竜太
元小学校教員。Web制作会社のフロントエンドエンジニアを経て現在はmicroCMSのカスタマーエンジニアをしています。趣味はバス釣り🎣