LoginSignup
13
9

More than 3 years have passed since last update.

Next.js + microCMS で dynamic route / preview 機能を実装する

Last updated at Posted at 2020-05-03

はじめに

最近JAMstackで開発している人をたくさん見るようになり、Headless CMSがちらほらでてきたり、フレームワークがより開発しやすくなるように整備を始めたりしていて、大規模以外の案件等ではJAMstackを選択することが増えてくるのかな、と思っています。
Next.js も9.3からSSG用にgetStaticPropsgetStaticPathsが導入されたり、プレビュー機能が導入されたりと、よりJAMstackで開発しやすくなるようになってきているようです。

今回だいたい最小構成を作ったので解説です。
preview機能だけ書こうかと思いましたが、getStaticProps等を使っているリファレンスがあまりなかったので、動的ルーティングからの手順を書いています。

TypeScript覚えたてなので、型の指定の仕方が間違っていたりしたらすみません。

ーーーー05/04追記ーーー
Twitterで、SSGでもapi routeが使えるとのご指摘をいただきまして内容を多少修正しました。
柴田さん、ありがとうございます。

対象読者

  • microCMSでアカウントを作ってちょっと触ったことがある
  • Next.jsを使ったことがある

今回TypeScriptで開発しましたが、使ってない人は適宜型などを削除して読んでいただければと思います。

環境

  • Windows 10 pro
  • node.js 12.14.1

リポジトリ

Githubリポジトリはこちらです。
なお、こちらのリポジトリはqiita用に環境変数を削除して公開しているものなので、
環境変数を正しくいれるとうごきます。(microCMSの情報)

全体の流れ

  1. microCMSでAPIを作る
  2. Next.jsでmicroCMS連携、dynamic routeのページを作る
  3. Nowでデプロイする、microCMSでwebhookの設定をする
  4. Next.jsでプレビュー用のapi routeを作る
  5. microCMSから画面プレビューする

1. microCMSでAPIを作る

API名:ブログ(違っても可)
エンドポイント:blogs

APIスキーマは以下の画像のように設定してください。
api_schema.PNG

ここで、以下の情報をメモしてください。

「APIリファレンス」タブのエンドポイントのX-API-KEY
「試してみる」ボタンを押してでてくるhttps://~~~~~~/api/v1/までのURL(v1でない場合もあり)

2. Next.jsでmicroCMS連携、dynamic routeのページを作る

環境構築

こちらの記事等を参考にして、Next.jsの環境を作ってください。

axiosとdotenvを使うので、モジュールを追加してください。

yarn add axios dotenv

next 9.1からsrcpagesをいれることができるようになったので、root/src/pages/の形にします。
next.config.jsを作り、以下のように記述します。

const { resolve } = require('path')
require('dotenv').config()

const nextConfig = {
  webpack: (config) => {
    config.resolve.alias['~'] = resolve(__dirname, 'src')
    return config
  },
  exportTrailingSlash: true,
  env: {
    API_KEY: process.env.API_KEY,
    SECRET_KEY: process.env.SECRET_KEY,
    END_POINT: process.env.END_POINT
  }
}

module.exports = nextConfig

コンポーネント等をimportするときにsrcからのパスで記述できるようにする設定と、環境変数の設定です。

ルートに.envファイルを作り、先ほどメモした情報をコピーします。

API_KEY=xxxx
END_POINT=xxxx

ページを作成

先ほど作ったAPIスキーマに合わせて、interfaceを定義するファイルを作ります。

src/interfaces/index.ts
export interface Blogs {
  id: string
  createdAt: string
  title: string
  label: string
  description: string
}

pagesディレクトリにblogsディレクトリを作り、[id].tsxファイルを作成、以下のように入力します。
データの取得等はこのあと追加するので、とりあえずページを表示する部分です。

src/pages/blogs/[id].tsx
import { Blogs } from '~/interfaces'
import { NextPage, GetStaticPaths, GetStaticProps } from 'next'
import Head from 'next/head'
import axios from 'axios'
import Link from 'next/link'

interface Props {
  blog: Blogs
  errors?: string
}

const BlogDetail: NextPage<Props> = (props) => {
  return (
    <>
      <Head>
        <title>ブログ詳細</title>
      </Head>
      <h1 className="title">ブログ詳細</h1>
      <Link href="/blogs/">
        <a className="link">ブログトップへ</a>
      </Link>
      <div className="item">
        <h2 className="item__title">{props.blog.title}</h2>
        <p className="item__label">{props.blog.label}</p>
        <p className="item__description">{props.blog.description}</p>
      </div>
    </>
  )
}

export default BlogDetail

getStaticPropsgetStaticPathsはnext 9.3で導入された新APIで、SSGのために作られたものです。
これまではgetInitialPropsをつかっていましたが、こちらを使うことにより完全に静的化されたファイルが生成されます。
getServerSidePropsも用意されており、SSGするとき以外ではこちらを使うことになりそうです。
これらはpages内にあるファイルでしか使用できません。

dynamic routeのパス名を指定する

先ほどのファイルに以下を追加します。
getStaticPathsは、dynamic routeで生成されるパス名を指定するためのものです。
microCMSからデータを取得してきて、id名をパス名として返しています。
GetStaticPathsの型の中身を見るとわかりますが、文字列の配列を返す必要があります。
microCMSはデフォルトで返ってくるデータの数は10個になっているので、パラメータとして?limit=9999を指定して全てのデータをとってこれるようにしています。

src/pages/blogs/[id].tsx
export const getStaticPaths: GetStaticPaths = async () => {
  const key = {
    headers: { 'X-API-KEY': process.env.API_KEY }
  }
  const res = await axios.get(process.env.END_POINT + 'blogs/?limit=9999', key)
  const data: Array<Blogs> = await res.data.contents
  const paths = data.map((item) => ({
    params: { id: item.id.toString() }
  }))

  return { paths, fallback: false }
}

データの受け渡しをする

先ほどのファイルに以下を追加します。
getStaticPropsはサーバ上でデータを取得し、ページに渡すためのAPIです。
getServerSidePropsではなくこちらを使うことにより、最適な静的サイトを生成することができるようです。

paramsとして、このページのPath名を取得できます。
パス名はid名にしているので、microCMSにそのid名のエンドポイントを指定し、一件だけのデータを取得してページに返すようにしています。

src/pages/blogs/[id].tsx
export const getStaticProps: GetStaticProps = async ({ params }) => {
  const key = {
    headers: { 'X-API-KEY': process.env.API_KEY }
  }
  const res = await axios.get(
    process.env.END_POINT + 'blogs/' + params?.id,
    key
  )
  const data: Blogs = await res.data
  return {
    props: { blog: data }
  }
}

これで[id].tsxは完成です。

それぞれのページへのリンクを持ったブログのトップを作る

blogsのindexを作ります。以下のファイルを作成してください。

src/pages/blogs/index.tsx
import Head from 'next/head'
import { Blogs } from '~/interfaces'
import axios from 'axios'
import { NextPage, GetStaticProps } from 'next'
import Link from 'next/link'

interface Props {
  blogs: Array<Blogs>
}

const BlogHome: NextPage<Props> = ({ blogs }) => (
  <>
    <Head>
      <title>blogs</title>
    </Head>

    <h1 className="title">ブログトップ</h1>
    <Link href="/">
      <a className="link">ホームへ</a>
    </Link>
    <div>
      {blogs.map((blog, index) => (
        <div className="item" key={index}>
          <h2 className="item__title">{blog.title}</h2>
          <p className="item__label">{blog.label}</p>
          <Link href="/blogs/[id]" as={`/blogs/${blog.id}`}>
            <a className="item__link">詳細へ</a>
          </Link>
        </div>
      ))}
    </div>
  </>
)

export const getStaticProps: GetStaticProps = async (): Promise<{
  props: Props
}> => {
  const key = {
    headers: { 'X-API-KEY': process.env.API_KEY }
  }
  const res = await axios.get(process.env.END_POINT + 'blogs/?limit=9999', key)
  const data: Array<Blogs> = await res.data.contents
  return {
    props: {
      blogs: data
    }
  }
}
export default BlogHome

3. Nowでデプロイする、microCMSでwebhookの設定をする

Now(現Vercel)では.envファイルが自動的に除外されるので、nowでの環境変数を宣言するファイルを作ります。
ルートに、now.jsonを作り、.envファイルと同じ情報を入力します。

{
  "build": {
    "env": {
      "API_KEY": "xxxx",
      "END_POINT": "xxxx"
    }
  }
}

githubとnowを連携して、nowで新しいプロジェクトを作ります。

https://vercel.com/login
こちらからgithubのアカウントでログインし、「Import Project」を選択します。
「From Git Repository」があるので、こちらから先ほど作成したgithubのリポジトリを選択します。
ビルドのコマンドは、「yarn build」にしてください。
自動的にビルドされURLが表示されるので、そちらにアクセスしちゃんと表示できていれば完了です。

microCMSでwebhookを設定する

静的に生成されたアプリ(getInitialPropsgetServerSidePropsがないページは自動で静的に生成される)は、build時にmicroCMSからデータをとってきてページを生成しているので、
microCMSが変更されたら再ビルドするようにmicroCMS側にwebhookを設定する必要があります。(アクセス時にmicroCMSからデータとってこないので)
nowのProject→Settings→Git Integration に「Deploy Hooks」があるので、

  • Hook Name:なんでもよい
  • Git Branch Name: 連携しているGithub のブランチ名(ブランチ切ってないならmaster)

を入力して「Create Hook」してください。
URLが生成されるので、それをコピーします。

microCMSの管理画面で、「API設定」タブの「Webhook」で
「カスタム通知」を選択し、先ほどのURLを貼り付けます。
「コンテンツの公開時」「コンテンツの削除時」にチェックをつけ、追加します。

4. Next.jsでプレビュー用のapi routeを作る

先ほどのpreview-modeのドキュメントを見ると、secret keyを設定してapi側で判定するように書いてあるので、環境変数に追加します。
なんでも好きな文字列で結構です。
now.jsonも同じように変更するのを忘れないでください。

.env
SECRET_KEY=xxxxxxx

pages/api/preview.tsを作成し、以下のように入力します。

src/pages/api/preview.ts
import { NextApiHandler } from 'next'
import axios from 'axios'

const preview: NextApiHandler = async (req, res) => {
  // クエリの確認
  if (
    req.query.secret !== process.env.SECRET_KEY ||
    !req.query.id ||
    !req.query.draftKey
  ) {
    return res
      .status(401)
      .json({ message: `Invalid query, ${process.env.SECRET_KEY}` })
  }

  // 下書きのデータを取得
  const key = {
    headers: { 'X-API-KEY': process.env.API_KEY }
  }
  const url =
    'https://next-test.microcms.io/api/v1/blogs/' +
    req.query.id +
    `?draftKey=${req.query.draftKey}`
  const post = await axios.get(url, key)

  // エラー処理
  if (!post) {
    return res.status(401).json({ message: 'Invalid draft key' })
  }

  // プレビューデータを格納
  res.setPreviewData({
    draftKey: req.query.draftKey,
    id: req.query.id
  })

  // 詳細ページへリダイレクト
  res.writeHead(307, { Location: `/blogs/${req.query.id}` })

  res.end('Preview mode enabled')
}

export default preview

res.setPreviewDataを設定すると、プレビューモードとなり、アプリ内のページから context.previewtrue として判別できるようになります。
また、オブジェクトとして値を渡すと、context.previewDataとしてアプリ内のページからアクセスできるようになります。
値をセットしたら、下書きの詳細ページへリダイレクトするようにします。一瞬404になってしまいますが、きちんと詳細ページがレンダリングされることが確認できました。

ページの処理を書く

引数のpreviewtrueだった時の処理を追加していきます。
ポイントとして、microCMSは下書きのデータを取得する際にはその下書きのdraftKeyをパラメータとして指定しないといけないので、urlに追加するようにします。

src/pages/blogs/[id].tsx
// ...
// 省略
export const getStaticProps: GetStaticProps = async ({
  params,
  preview,
  previewData
}) => {
  const key = {
    headers: { 'X-API-KEY': process.env.API_KEY }
  }
  let url = process.env.END_POINT + 'blogs/' + params?.id
  // 下書きは draftKey を含む必要があるのでプレビューの時は追加
  if (preview) {
    url += `?draftKey=${previewData.draftKey}`
  }
  const res = await axios.get(url, key)
  const data: Blogs = await res.data
  return {
    props: { blog: data }
  }
}

指定したパス名以外でも通るように修正

getStaticPathsの戻り値で、return { paths, fallback: false }としていましたが、
ここのfallbacktrueにします。

fallback を falseにすると、pathsとして返したパス以外はすべて自動的に404になってしまいます。
プレビューモードでdynamic routeにアクセスするときは、当然ここで指定したパス以外のページにルーティングされるため、falseのままだと404になってしまいます。
fallback を trueにすることで、ここで指定したパス以外で入ってきたときも通すようにできるようになります。
しかし、どんなパスでも通るようになるということなので、ページを表示するほうでエラー処理を追加します。

src/pages/blogs/[id].tsx
// ... 省略
import ErrorPage from 'next/error'
// ... 省略
const BlogDetail: NextPage<Props> = (props) => {
  if (!props.blog) {
    return <ErrorPage statusCode={404} />
  }
// ... 省略

ブログのトップページのほうも、プレビュー時にはプレビューのデータを追加するように処理を追加します。

src/pages/blogs/index.tsx
// ...
// 省略
export const getStaticProps: GetStaticProps = async ({
  preview,
  previewData
}): Promise<{
  props: Props
}> => {
  const key = {
    headers: { 'X-API-KEY': process.env.API_KEY }
  }
  const res = await axios.get(process.env.END_POINT + 'blogs/?limit=9999', key)
  const data: Array<Blogs> = await res.data.contents
  // プレビュー時は draft のコンテンツを追加
  if (preview) {
    const draftUrl =
      process.env.END_POINT +
      'blogs/' +
      previewData.id +
      `?draftKey=${previewData.draftKey}`
    const draftRes = await axios.get(draftUrl, key)
    data.unshift(await draftRes.data)
  }
  return {
    props: {
      blogs: data
    }
  }
}
export default BlogHome

これでプレビュー用のAPI routeの設定完了です。

5. microCMSから画面プレビューする

先ほどの変更をpush し、nowでデプロイします。
アプリのURLをコピーし、microCMS側で設定します。

microCMSの「API設定」の「画面プレビュー」で、先ほどのURLを入力、secretを自分で決めたsecret keyの文字列を入力してください。
上手くいかない場合は、URLのところをlocalhost:3000等にしてみて、ローカルで検証すると良いです。
microCMS_preview.PNG

コンテンツを追加する部分で、下書きを追加し、「画面プレビュー」をクリックすると、プレビュー用に設定したURLに飛び、下書きが追加された状態のアプリを見ることができます。

最後に

少し長くなってしまいましたが、なにか間違っているところなどあればお知らせいただけますと幸いです。

13
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
9