Qlitre's Blog

2022.08.11 Next.js /microCMS

Next.js + microCMSでプレビュー画面を実装する方法

Next.js + microCMS + Vercelの構成でプレビュー機能を実装する方法についてです。

参考

Next.jsのPreview Mode+Vercelでプレビュー機能を実現する

microCMSの設定

まずはAPI設定→画面プレビューから遷移先URLを設定します。




URLは以下のようにします。

https://www.your-domain.com/api/preview?slug={CONTENT_ID}&draftKey={DRAFT_KEY}


microCMSで記事を書いている際に、画面プレビューリンクを押すと、こちらのURLが表示されます。

プレビュー用関数の作成

次にpages→api配下にpreview.tsを作成します。

/* pages/api/preview.ts */
import { NextApiRequest, NextApiResponse } from 'next'
import { client } from 'libs/client'

const preview = async (req: NextApiRequest, res: NextApiResponse) => {
    const { draftKey, slug } = req.query    
    if (typeof draftKey !== 'string' || typeof slug !== 'string') {
        res.status(404).end()
        return
    }

    const data = await client.get({
        endpoint: 'post',
        contentId: slug,
        queries: {
            draftKey,
        },
    })

    if (!data) {
        return res.status(401).json({ message: 'Invalid slug' })
    }
  
    res.setPreviewData({
        slug: data.id,
        draftKey: req.query.draftKey,
    });
    res.writeHead(307, { Location: `/post/${data.id}` })
    res.end('Preview mode enabled')
}

export default preview


preview用のURLに遷移した際に、{CONTENTID}と{DRAFTKEY}を渡して記事詳細URLにリダイレクトさせる、という処理になります。

記事詳細ページの編集

次に記事詳細ページの編集をします。まずはgetStaticProps関数です。

/* pages/post/[slug].tsx */
export const getStaticProps: GetStaticProps = async (context) => {
  const { params, previewData } = context
  if (!params?.slug) {
    throw new Error('Error: ID not found')
  }

  /* draftKeyの存在チェック関数 */
  type Draft = {
    draftKey: string
  }

  const isDraft = (arg: any): arg is Draft => {
    if (!arg?.draftKey) {
      return false
    }
    return typeof arg.draftKey === 'string'
  }

  const slug = String(params.slug);
  /* requestのクエリパラメータを生成*/
  const draftKey = isDraft(previewData)
    ? { draftKey: previewData.draftKey }
    : {}

  /* draftKeyを付与してリクエストを投げる */
  try {
    const data = await client.getListDetail<Post>({
      endpoint: "post",
      contentId: slug,
      queries: draftKey
    });
    return {
      props: {
        post: data,
        ...draftKey,
      },
    };
  } catch (e) {
    /* 失敗したら404 */
    return { notFound: true }
  }
};


リダイレクトした際のパラメータからdraftKeyを取得し、存在する場合は付与してリクエストを投げる…ということをやっています。

次にgetStaticPath関数のfallbackをtrueにします。

export const getStaticPaths: GetStaticPaths = async () => {
  ...
  return { paths, fallback: true };
};


後はhtmlを自身の環境に合わせて編集します。

export default function Article({ post, draftKey }: Props) {
  return post ? (
    <>      
      {/* プレビューモードであるという表示 */}
      {draftKey && (
        <div>
          現在プレビューモードで閲覧中です。
        </div>
      )}
   {/* 記事本文 */}
      <PostDetailContent post={post} />
    </ >
  ) : (
    <div>no content</div>
  )
}


開発環境で確認をする

開発環境を起動させて以下のURLにアクセスしてみましょう。

http://localhost:3000/api/preview?slug=your-slug&draftKey=your-draft-key


draftKeyは下書き中の記事の左上の方にあります。




記事詳細ページにリダイレクトされることを確認します。


本番環境でうまくいかない場合


自分の場合、開発環境では表示されたものの、肝心の本番環境で表示されない事象がありました。

Vercelのデプロイログを見てみると、以下のような警告が出ていました。

warn  - Statically exporting a Next.js application via `next export` disables API routes.


build時にnext exportするとAPI routeが働かないみたいです。
package.jsonのbuildコマンドがこうなっていたのが原因です。

"build": "next build && next export && next-sitemap --config sitemap.config.js",


next exportを取り除くと正常に動きました。

"build": "next build && next-sitemap --config sitemap.config.js",