Next.jsのプレビューモードを利用して、プレビュー環境を作成する

このチュートリアルでは、Next.jsの Preview Mode と、Newtのプレビュー設定を利用して、プレビュー環境を作成する手順を紹介します。

前提条件

  1. Next.jsの v9.3 以上を利用して、サイトを作成していること
  2. 作成したサイトがデプロイ済みであること
  3. Next.jsの API Routes について理解していること
  4. NewtのJS SDKである newt-client-js の基本的な利用方法について理解していること
  5. newt-client-jsの v3.1.0 以上を利用していること

概要

Next.jsのPreview Modeを利用して、プレビュー用のAPIルートを作成し、プレビューデータを取得できるようにします。
また、Newtのコンテンツ編集画面から、作成したプレビュー環境にアクセスできるようにします。

nextjs_preview.jpg

ここでは、以下の流れでプレビュー処理を行うものとして、実装を進めていきます。

  • Newtの管理画面から「プレビュー」ボタンをクリックする
  • プレビュー用のAPIルート /api/preview に、secretslug のクエリパラメータをつけてアクセスする
  • secretslug の値を検証し、問題なければコンテンツ詳細ページ /article/{slug} にリダイレクトする
  • コンテンツ詳細ページの getStaticProps でプレビューデータを取得して表示する

もしパスが異なる場合は、適宜読み替えながらチュートリアルを進めてください。

1. Newt API Tokenを作成する

はじめに、Newtの管理画面に入り、スペース設定 > APIキー のページからNewt API Tokenを作成します。
※ 下書き中のコンテンツを取得するためには、Newt APIを利用します。

nextjs_preview2.jpg

名前と取得対象を決めて「作成」を押します。

nextjs_preview3.jpg

2. プレビューデータの取得メソッドを作成する

プレビューデータを取得する getArticleBySlug を実装します。引数に preview を渡し、true の場合は下書きを含む全コンテンツを取得、false の場合は公開コンテンツのみを取得するようにします。
newt-client-js を利用します。

'YOUR_NEWT_API_TOKEN' のところに1で作成したNewt API Tokenを設定します。
'YOUR_NEWT_CDN_API_TOKEN' のところにはNewt CDN API Tokenを設定してください。
spaceUid・appUid・modelUidには、プレビューデータの取得対象の値を設定します。

// lib/api.js

import { createClient } from 'newt-client-js'

// Newt CDN APIのクライアント(公開コンテンツのみ取得)
const newtCdnClient = createClient({
  spaceUid: 'YOUR_SPACE_UID',
  token: 'YOUR_NEWT_CDN_API_TOKEN',
  apiType: 'cdn',
})

// Newt APIのクライアント(全コンテンツ取得)
const newtApiClient = createClient({
  spaceUid: 'YOUR_SPACE_UID',
  token: 'YOUR_NEWT_API_TOKEN',
  apiType: 'api',
})

export async function getArticleBySlug(slug, preview) {
  const client = preview ? newtApiClient : newtCdnClient
  const article = await client.getFirstContent({
    appUid: 'YOUR_APP_UID',
    modelUid: 'YOUR_MODEL_UID',
    query: { slug },
  })
  return article
}

TypeScriptで実装する場合は、以下のようになります。
getArticleBySlug の引数で Promise<Article | null> と指定していますが、このArticle の型は、Newtの管理画面で設定したモデル情報をもとに定義してください。

// lib/api.ts

import { createClient } from 'newt-client-js'

// Newt CDN APIのクライアント(公開コンテンツのみ取得)
const newtCdnClient = createClient({
  spaceUid: 'YOUR_SPACE_UID',
  token: 'YOUR_NEWT_CDN_API_TOKEN',
  apiType: 'cdn',
})

// Newt APIのクライアント(全コンテンツ取得)
const newtApiClient = createClient({
  spaceUid: 'YOUR_SPACE_UID',
  token: 'YOUR_NEWT_API_TOKEN',
  apiType: 'api',
})

export async function getArticleBySlug(
  slug: string,
  preview: boolean
): Promise<Article | null> {
  const client = preview ? newtApiClient : newtCdnClient
  const article = await client.getFirstContent<Article>({
    appUid: 'YOUR_APP_UID',
    modelUid: 'YOUR_MODEL_UID',
    query: { slug },
  })
  return article
}

3. プレビュー用のAPIルートを作成する

プレビュー用のAPIルートを作成します。ここではエンドポイントが /api/preview となるように、pages/api/preview.js というファイルを用意します。この処理では、以下のことを行います。

  • リクエストが有効なものか、クエリパラメータのsecretの値で検証する(ここでは PREVIEW_SECRET という環境変数を利用する)
  • slugと対応するコンテンツがあるか検証する
  • Cookieを設定し、プレビューモードを有効にする
  • 取得した情報からパスを指定してリダイレクトする
// pages/api/preview.js

import { getArticleBySlug } from '../../lib/api'

export default async (req, res) => {
  // secretを検証する、slugパラメータの有無を検証する
  if (req.query.secret !== process.env.PREVIEW_SECRET || !req.query.slug) {
    return res.status(401).json({ message: 'Invalid token' })
  }

  // slugと対応するコンテンツがあるか検証する
  const article = await getArticleBySlug(req.query.slug, true)
  if (!article) {
    return res.status(401).json({ message: 'Invalid slug' })
  }

  // Cookieを設定し、プレビューモードを有効にする
  res.setPreviewData({})

  // 取得した情報からパスを指定してリダイレクトする
  res.redirect(`/article/${article.slug}`)
}

TypeScriptで実装する場合は、以下のようになります。

// pages/api/preview.ts

import type { NextApiRequest, NextApiResponse } from 'next'
import { getArticleBySlug } from '../../lib/api'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  // secretを検証する、slugパラメータの有無を検証する
  if (req.query.secret !== process.env.PREVIEW_SECRET || !req.query.slug) {
    return res.status(401).json({ message: 'Invalid token' })
  }

  // slugと対応するコンテンツがあるか検証する
  const article = await getArticleBySlug(req.query.slug + '', true)
  if (!article) {
    return res.status(401).json({ message: 'Invalid slug' })
  }

  // Cookieを設定し、プレビューモードを有効にする
  res.setPreviewData({})

  // 取得した情報からパスを指定してリダイレクトする
  res.redirect(`/article/${article.slug}`)
}

4. getStaticPropsを更新する

次に、コンテンツ詳細ページの getStaticProps を更新します。
プレビューモードのCookieが設定されている場合、getStaticProps がビルド時ではなく、リクエスト時に呼び出されます。
また、引数として渡される context オブジェクトの context.preview の値が true となります。
2で作成した getArticleBySlug を利用して、以下のように実装します。

// pages/article/[slug].js

export async function getStaticProps(context) {
  const { params, preview } = context
  const article = await getArticleBySlug(params.slug, preview)
  return {
    props: {
      article,
    },
  }
}

TypeScriptで実装する場合は、以下のようになります。

// pages/article/[slug].tsx

type Context = {
  params: {
    slug: string
  }
  preview?: boolean
}

export async function getStaticProps(context: Context) {
  const { params, preview = false } = context
  const article = await getArticleBySlug(params.slug, preview)
  return {
    props: {
      article,
    },
  }
}

これでNext.jsの設定は終了です。変更をコミットして、デプロイしておきましょう。

5. プレビュー設定を行う

最後に、Newtの管理画面に入り、プレビュー設定を行います。
モデル設定の右上から「プレビュー設定」に進みます。

プレビュー用のAPIルート /api/previewsecretslug のクエリパラメータをつけてアクセスするよう、プレビューURLを指定します。
サイトのドメインが https://nextjs-preview.newt.so、secretが hogehoge の場合、プレビューURLは以下のように指定します。

https://nextjs-preview.newt.so/api/preview?secret=hogehoge&slug={slug}

モデルが slug というフィールドを持つ場合、{slug} のように記載することで、各コンテンツのslugの値がプレビューURLに展開されます。

nextjs_preview4.jpg

以上ですべての設定ができました。
コンテンツ編集画面からプレビューが見れるか確認しましょう。

もし、プレビューが見れない場合は、プレビューURLが正しく指定されているか、tokenやsecretの値が正しいか確認してみてください。

また、Next.jsのプレビュー機能について、より詳しく確認したい方は、Vercelの Preview Mode をご確認ください。

Newt Made in Newt