yuheijotaki.com

Newtで登録したコンテンツをNext.jsで表示する

前回の記事 にて、Next.jsのHello WorldからVercelへのデプロイまでを行いました。
今回はヘッドレスCMSを用いて登録したコンテンツの表示までを行ってみます。

構成

  • フロントエンド:Next.js
  • ヘッドレスCMS:Newt
  • ホスティング:Vercel

Newt は昨年にローンチされた国産のヘッドレスCMSで、元プレイドの方々が「次のWordPressをつくる」をミッションとして開発されているサービスです。

競合として同じく国産の microCMS は以前に触ったことがあったため、今回は比較も兼ねてNewtを使用してみることにしました。

やってみる

ざっくり次のような流れになります。

  • Newtで投稿の枠を作る
  • Newtで投稿をする
  • 投稿したコンテンツのAPIをたたく
  • Next.jsで表示させる
  • GitHubにプッシュする
  • 自動でVercelへビルドとデプロイする

Newtでコンテンツ登録

まずはCMS側でコンテンツ(データ)を登録しておきます。

アカウントを登録とスペース作成

サインアップ画面 からアカウントを登録後、スペースを作成します。
スペースはひとつのプロジェクト単位にあたります。

Appの追加

Appを追加します。 https://storage.googleapis.com/newt-files/ac34dcef-2b25-4985-b157-d8017049a930/app.png Appは簡単に言うとモデルとビューを含んだモジュールで、ひとつのスペースにAppをいくつか登録する構成になります。
例えば「お知らせ」Appと「制作実績」Appでそれぞれ分けて作成するようなイメージで、WordPressのカスタム投稿タイプに粒度基準の考え方は近そうです。

Appには、

  • テンプレートから追加
  • タイプを選択して追加
  • ファイルから追加

の3種類があります。

テンプレート

モデル、ビュー、ダミーコンテンツが予めセットされたAppテンプレートを管理画面から1クリックでインストールできます。GitHubに公開されているスターターと組み合わせて使う事ができます。

例えばブログの一覧/詳細/検索結果セットのテンプレートがあり、細かい要件がなく素早く作りたいなどの際に使えそうです。

タイプ

タイプは、モデルとビューを自身で作成する際に使用します。
今回はこのタイプの「CMS App」を使用します。

ファイル

Newt上で管理しているモデル、ビュー、コンテンツ、メディア等のデータを、App単位でエクスポート(JSON形式)することができます。

ファイルは使い所がしっくりきていませんが、無料プランでは使用できません。

CMS Appのモデル作成

CMS Appを追加します。

左のナビゲーションに「test-app」が追加されたので、モデルを作成します。

今回はサンプルのため適当なモデル名で作成します。

フィールドの作成

モデルができたら、フィールドを作成します。

画面右側にフィールドの種類がでているので、設定をしていきます。

追加すると一覧に表示されます。

JSONプレビューに切り替えると、JSONのサンプルが確認できます。

コンテンツ登録

取り急ぎフィールドはここまでで、次にコンテンツを登録します。

スペースのトップに戻って、「test-modelを追加」を押します。

コンテンツ登録をします。

「保存して公開」を押すと一覧に表示されます。

テストで3件公開してみました。

コンテンツ登録については以上です。

APIを取得する

主に クイックスタート の通りに進めていきます。

APIデータ取得

エンドポイントは下記のフォーマットになるので、

https://{spaceUid}.cdn.newt.so/v1/{appUid}/{modelUid}

今回の場合、下記に置き換えます。

https://next-sample.cdn.newt.so/v1/test-app/test-model

APIキーの発行を「スペース設定」>「APIキー」より行います。

Newt CDN API から「作成」します。
取得対象のカスタマイズ(○○Appのみ、など)も可能です。

curl -H "Authorization: Bearer {YOUR_TOKEN}" \
  -X GET "https://next-sample.cdn.newt.so/v1/test-app/test-model"

フォーマットに合わせてcurlでリクエストすると登録したコンテンツのレスポンスが返ってきました。

API取得については以上です。

Next.jsでコンテンツ表示

Next.jsを利用して登録コンテンツの表示を行います。
前回までに create-next-app コマンドでプロジェクトを作成していたので、そのリポジトリを使用します。

主に チュートリアル 通りに進めていきます。

環境変数の設定

.env.local ファイルを作成しスペースUIDとトークンの定義をします。

NEWT_SPACE_UID=next-sample
NEWT_CDN_API_TOKEN=xxxxxxxxxxxxxxx

newt-client-js のインストール

Newt用のJavaScriptクライアントSDKをインストールします。

npm install newt-client-js

APIクライアントの作成

インストールしたSDKを使ってクライアントを作成します。

▼ コードを展開する

// lib/newt.js

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

const client = createClient({
  spaceUid: process.env.NEWT_SPACE_UID + '',
  token: process.env.NEWT_CDN_API_TOKEN + '',
  apiType: 'cdn',
})

一覧ページの作成

投稿一覧の取得メソッドを作成し、一覧を表示します。

▼ コードを展開する

// lib/newt.js

export const getArticles = async () => {
  const { items } = await client.getContents({
    appUid: 'test-app',
    modelUid: 'test-model',
    query: {
      select: ['_id', 'title', 'slug', 'body'],
    },
  })
  return items
}
// pages/index.js

import Head from 'next/head'
import Link from 'next/link'
import { getArticles } from '../lib/newt'

export default function Home({ articles }) {
  return (
    <>
      <Head>
        <title>Newt・Next.jsブログ</title>
        <meta name="description" content="NewtとNext.jsを利用したブログです" />
      </Head>
      <main>
        <ul>
          {articles.map((article) => {
            return (
              <li key={article._id}>
                <Link href={`test-model/${article.slug}`}>{article.title}</Link>
              </li>
            )
          })}
        </ul>
      </main>
    </>
  )
}

export const getStaticProps = async () => {
  const articles = await getArticles()
  return {
    props: {
      articles,
    },
  }
}

詳細ページの作成

投稿詳細の取得メソッドを作成し、一覧を表示します。

▼ コードを展開する

// lib/newt.js

export const getArticleBySlug = async (slug) => {
  const article = await client.getFirstContent({
    appUid: 'test-app',
    modelUid: 'test-model',
    query: {
      slug,
      select: ['_id', 'title', 'slug', 'body'],
    },
  })
  return article
}
// test-model/[slug].js

import Head from 'next/head'
import { getArticles, getArticleBySlug } from '../../lib/newt'

export default function Article({ article }) {
  return (
    <>
      <Head>
        <title>{article.title}</title>
        <meta name="description" content="投稿詳細ページです" />
      </Head>
      <main>
        <h1>{article.title}</h1>
        <div dangerouslySetInnerHTML={{ __html: article.body }} />
      </main>
    </>
  )
}

export const getStaticPaths = async () => {
  const articles = await getArticles()
  return {
    paths: articles.map((article) => ({
      params: {
        slug: article.slug,
      },
    })),
    fallback: false,
  }
}

export const getStaticProps = async ({
  params,
}) => {
  const { slug } = params
  const article = await getArticleBySlug(slug)
  return {
    props: {
      article,
    },
  }
}

Vercelへのデプロイ

Next.jsのコードをVercelへデプロイします。

環境変数の設定

プロジェクトトップ > 「Settings」 > 「Environment Variables」から NEWT_SPACE_UIDNEWT_CDN_API_TOKEN を設定します。

デプロイ

ブランチへプッシュ、デプロイして無事にサクセスになりました。
https://next-sample-yuheijotaki.vercel.app/

感想など

Newt

  • スターター という、モデルやダミーコンテンツ、フロントエンド(Nuxt.jsやNext.js)がセットになったパッケージが便利そう。
    • 本当にパッと作りたいときや、たたきとして作る際に使えそう。
  • ドキュメントやSDKも用意されているので、変わった要件でなければ問題なく使えそうという所感。
    • 下書きやフィールドの指定等は簡単なことしか試してないため、案件で使用する際はもう少し検証が必要。
    • コンテンツ画像を 外部ストレージ機能 でS3やCloud Storageに置けるのがmicroCMSとの違いになりそうで、そこらへんも使ってみたい。
    • ググったときに見つかる記事などは現状だと断然microCMSの方が多い。
  • microCMSも同様だが、Newtのスペース、App、モデルの概念を理解した上でCMS設計していく必要があり。
    • ここも実際にサンプルでもう少し手を動かしたり試してみないとつかめなさそうだと感じた。

Next.js

  • 詳細ページの /**/[slug].js など、ディレクトリで分ける方法はVueのNuxt.jsと変わりなく、これくらいのレベルだと違和感なく作れた。
    • しまぶーのYouTube にReact(Next.js)講座があったので、とりあえずコード的なところはそちらで学びたいと思います。
  • 今回はJSやSSGでの作成でしたので、機会があればTypeScriptやSSRも試してみたい。