Sanity CMS + Next.js(React) でのサイトの作り方

本稿ではNext.jsをフロントエンドに、ヘッドレスCMSの一つであるSanity CMSをバックエンドとしてサイトを構築する方法をご紹介します。具体的には、ヘッドレスCMSの用途、Sanityのアカウント作成から、ヘッドレスCMSの構造、Sanity Studioの使い方、クエリ言語GROQ、フロントエンドでの実装Sanityまでを解説しています。 本稿を読めば公式ドキュメントを難なく理解できると思います。

ヘッドレスCMSの用途

Next.js又はReactのメリットとの兼ね合いにはなりますが、ブログであればSanity CMSではなく、ブログに特化した機能の充実しているワードプレスをそのまま使った方がいいような気がします。他方で、エンジニアではないクライアントからの依頼で、ブログ以外の定期的な更新を要するサイト(例:企業ホームページ)を開発して納入する場合、クライアントによる更新が容易なSanityなどのヘッドレスCMSとNext.js又はReactを使った構築は有力だと思います。Next.js又はReactの機能によって、クライアントの競合サイト(多くはワードプレスサイト)との差別化が可能になります。

本編

Sanity CMSを利用する前提として、Next.jsのプロジェクトまたは、Reactのプロジェクトが立ち上がっている必要があります。React及びNext.jsプロジェクトの作成方法は私のこちら「ReactでWebサイト又はWebアプリを作成する方法(初動)」の記事を参考にしてください。本稿ではNext.jsを前提に紹介します。

Sanityのアカウント作成

Sanityのホームページ(sanity.io)に行き、右上のログインをクリックします。


Sanity CMSアカウント作成 - ログイン

トップぺージでログインをクリックすると、次のようなサインアップの方法(ログインプロバイダー)を選ぶ画面になりますので、Google又はGithubでサインアップします。


sanity CMSアカウント作成 - ログイン

次に新しいプロジェクトを作成します。右上のCreate new projectをクリックします。左中央の青いボタンのCreate first projectをクリックすると、別のウィンドウで本稿の説明と同様の手順が表示されますが、新しいプロジェクトは作成されません。




Sanityのproject作成

上記のような画面が現れ、ローカルのNext.js又はReactのプロジェクトに”npm -y create sanity@latest”のコマンドを打つように指示されますが、先にグローバルにsanity/cliをインストールしてから、このコマンドを打ちます。まとめると次の2行です。

$ npm i -g @sanity/cli
$ npm -y create sanity@latest

Next.jsとSanity Studioのディレクトリー構成

1行目はグローバルなインポートなので実行する場所は気にする必要がありませんが、2行目は既存のnext.js(react)プロジェクトの内側に配置するのがよいのか(同一repo内)、next.js(react)プロジェクトと並列するのがよいのか(別のrepo)については、Sanity公式の掲示板で議論があります(こちら)。初期的にはいずれでも動きそうですが、筆者も結論に達しておりません。

Sanity Studioの立ち上げ

2行目を実行後、初めての場合はログインを求められます。ターミナルの指示に従って表示されるブラウザでログインを済ませます。ログイン後、ターミナル上では下記のようなプロジェクトの名前を入力するメッセージが表示されますので、任意のプロジェクト名を入力します。


Sanity Studioの立ち上げ

続いて、デフォルトのコンフィギュレーション設定を使用するか否かが聞かれます。デフォルト設定で問題ないので”Y”と入力しEnterを打ちます。”Project output path”が表示されれば順調です。

“Project output path”が表示されたところでEnterを押すと次のようにテンプレート(スキーマ)を選ぶメッセージが表示されます。E-Commerce、Blog、又はスキーマのない空のテンプレートが選べます。本稿の事例ではBlog(ブログ)を選択します(上下キーでターミナルを移動してEnterを押します)。”Project output path”については後ほど説明します。


Sanity Studioの立ち上げ - スキーマの選択

TypeScriptの使用有無を聞かれた場合は、”Y”又は”n”を押します。本稿事例ではTypeScriptを使用するので”Y”を選択します。続いてPakage manegerも聞かれるので、npmの方はnpmを、yarnの方はyarnを選択しEnterを押します。



以上で初期設定が終わり、ローカルのフロントエンドNext.jsプロジェクトに対する、バックエンドたるSanityプロジェクトが作成されました。また、バックエンドの管理画面であるSanity Studioもローカル環境にセットアップされています。Sanity Studioはワードプレスの管理画面に相当するものです。Sanityではバックエンドのデータの集合をdatasetと呼び、同じプロジェクト内で開発環境用のdataset、本番環境用のdatasetを持つことができます。まとめると、以上で下記のような環境が構築されました。

書き込みは管理画面に書き込むだけで可能です(ノーコード)。フロントエンドからの読み込みにはAPIを記述します。SanityはヘッドレスCMSですので、フロントエンド側はCMSの画面構造に制約されず、自由に組むことができます。


Sanityサーバー、Sanity Studio、フロントエンド、3者の関係(ヘッドレスCMSの構造)

Sanity Studio(管理画面)はそれ自体はフロントエンドです。ローカルに配置することも、任意の場所にデプロイすることもできます。ローカルでのディレクトリー構成は2種類あり得るのは前述の通りです。下記は内側に配置した様子で、Next.jsのプロジェクト(repo)の内側にSanity Studioのプロジェクト(これ自体もReact repository)が格納されています。


Sanity Studioのフォルダ構成 - 並列

上記のsanity-demo-2がSanity Studioのrepoです。展開すると、下記のようになります。


Sanity Studioのフォルダ構成 - 入れ子

両者を並列に配置すると下記のようなディレクトリー構成になります。名称が紛らわしく恐縮ながら、下記事例では、sanity-demoがnext.jsのプロジェクト(repo)、sanity-demo-3がSanity Studioのrepoです。


Sanity Studioのフォルダ構成 - 並列

Sanity Studioを立ち上げる方法

Sanity Studioは先ほど途中で表示された”Project output path”の中にあります。前述の通り、Sanity Studio自体はReactで作られているので、”Project output path”の中にcdで移動し、npm run devで起動します。

npm run dev

起動が成功すると、下記のようなログイン画面が表示されます。この画面が表示されない場合は、ターミナルで"sanity login"と打ってログインをします。

sanity login


Sanity Studioへのログイン

ログイン後には下記のようなSanity Studio(管理画面)が表示されます。下記のURLの欄の通り、Sanity StudioはSanityのサーバーではなくlocalhost上で動きます(本番環境では任意の場所にSanity StudioをReactのプロジェクトとしてデプロイします)。Sanity Studioのデフォルトのポート番号は3333です。localhostのポート番号3000にはフロントエンドのNext.jsアプリが稼働します。


Sanity Studio(初期のトップページ)

Sanity Studioの構成

下記はSanity Studioのプロジェクト内のschemas(スキーマ)です。先ほどブログでスキーマを設定したので、対応するjavascriptのオブジェクトのファイルが並びます。スキーマのファイルはauthor, category, post他があり、下記のVscodeの画面ではpostを表示しています。


Sanity Studioのプロジェクト内のschemas(スキーマ)

下記がSanity Studioの画面です。左側にスキーマに対応するPost, Author, Categoryがあり、右側はPostをクリックした画面です。Post内のTitleから始まる各項目は、上記のpost.ts内で定義され対応します。


Sanity Studioの画面

Sanity Studioへの書き込み

下記はSanity StudioでAuthor(筆者)を書き込んだ例です。左のサイドバーでAuthorを選択した後、右側の入力画面で名前、Slug(パーマリンク)、Image(アイコン画像)、Bio(自己紹介)を記入し、最後に右下のPublish(公開)を押します。公開されるとPublishedとのトーストが表示されて、右下のボタンの色はグレーに変わります。


Sanity Studioへの書き込み

次にPost(投稿)を書き込みます。CategoriesとPublished Dateのように空欄の項目があっても問題ありません。書き込みが終わったらPublished(公開)を押します。


Sanity Studioへの書き込み

以上で書き込みが完了しました(下記の①が完了)。この段階では、まだフロントエンドでAPIを記述していないので実際には未公開です。


Sanityサーバー、Sanity Studio、フロントエンドの構造

Sanity Studioからの読み込み(API)

本章以降の手順(下記の図では②)は、Sanityの公式ドキュメントを読むための前提知識と主要なコードのみを解説します。コードの詳細は引用する公式テキストでご確認ください。②の手順は標準的なAPIの呼び出し同様に、(1)コンフィギュレーションファイルの設定と、(2)同コンフィギュレーションを使用したfetchの2段階に分かれます。クエリ言語(GROQ)は(2)で使用しますが、始めに解説します。


Sanityサーバー、Sanity Studio、フロントエンド、3者の関係(ヘッドレスCMSの構造)

クエリ言語(GROQ)

Sanity Studioには、フロントエンド側での実装にそのままコピーして使えるGROQという専用のクエリ言語があります。また、GROQを使うと、フロントエンドでの実装前に、Sanity Studio上でクエリの結果を確認することもできます。GROQは初めて見ると難しそうに見えますが、慣れると簡単です。下記は、"post"スキーマに含まれる全てのデータについて、id, title, slug, author(authorについてはnameとbio), bodyを取得する例です。詳細はGROQのドキュメンテーションをご覧ください。

*[_type == 'post']{
_id,
title,
slug,
author -> {
name,
bio
},
body
}

1.コンフィギュレーションファイル設定

フロントエンドで使用するライブラリーのnext-sanityをインストールします。

npm i next-sanity

sanity.ts(又はclient.ts)

次にAPIキーに相当するものをフロントエンド側に設定します。フロントエンドのプロジェクト(本件ではNext.js)内のpackage.jsonと同じ階層に、sanity.ts(又はclient.ts)というコンフィギュレーションファイルを作成します。sanity.ts(client.ts)記載方法は公式ドキュメントのTutorial: Make a blog with Next.js, React, and Sanityをご覧ください(下記に結論のみ引用)。なお、キーはSanity Studio内のsanity.jsonに記載があります。sanity.ts(client.ts)の目的はSanityClientのエクスポートです。実際にバックエンドにAPIを要求するときにはこのSanityClientを使用します。

// client.ts
import sanityClient from '@sanity/client'

export default sanityClient({
projectId: 'your-project-id', // you can find this in sanity.json
dataset: 'production', // or the name you chose in step 1
useCdn: true // `false` if you want to ensure fresh data
})

次に公式ドキュメントに記載のある便利な関数を幾つか紹介します。

urlFor関数

先ほど引用した公式ドキュメントの下段で説明がある関数です。この関数はSanityのバックエンドからの応答である画像からURLを抜き取るめに、各投稿のファイルである、[slug].tsxで使用します。URLはimgタグで使用します。

import imageUrlBuilder from '@sanity/image-url'

function urlFor (source) {
return imageUrlBuilder(client).image(source)
}

useCurrentUser

現在Sanityにログインしているユーザーの情報を取得する関数です。この関数の詳細はこちらの公式ドキュメントuseCurrentUser Custom React Hookをご覧ください。

2.フロントエンドからAPIリクエス(Fetch)

Fetchを実行するページ(ファイル)にて、sanity.ts(又はclient.ts)からエクスポートしたSanityClinetをインポートします。先ほど引用した公式ドキュメントでは、import clinetとなっていて分かりにくいですが、import SanityClientが正しいです。

import SanityClient from '../../client'

次にGROQで作成したクエリをバックティックで囲って、変数(下記ではquyery)に格納します。公式ドキュメントではfetchの引数として直接GROQのクエリを記入していますが、クエリが長い場合はわかりにくいので、queryなどの変数に格納する方法をお勧めします。また、下記では先ほど例示したクエリを格納しています。実際には目的のデータに応じてクエリは毎回異なります。

const query = `*[_type == 'post']{
_id,
title,
slug,
author -> {
name,
bio
},
body
}`

sanityClientを使ってfetch(APIリクエスト)します。非同期処理になるので、fetch処理はasync関数の内側に配置します。ReactであればUseEffectの内側でasync関数を定義して実行、Next.jsであればSSRのgetServerSideProps又はgetStaticProps関数をasync関数とする方法が本件では有力だと思います。先ほど引用した公式ドキュメントではNext.jsのgetStaticProps関数をasync関数とし、その内側でfetchしています。fetchのコードは下記の通り、sanityClient.fetchとし、その引数にqueryを渡します。戻り値のJSONは、ここではresponseに格納しました。

const response = await sanityClient.fetch(query);

以降のコードは通常のfetchやaxiosを使ったAPIリクエストにて、responseを得た後のコードと共通ですので本稿では省略します。外部APIを取得して処理する方法は、こちらの記事「Reactでのaxiosの使い方【外部APIの取得方法】」で詳しく解説しているので参考にして頂ければ幸いです。同記事では、本稿のNext.jsのSSRではなくReactのuseEffectの内側でasync関数を定義し、axiosを使って外部のAPIに対してリクエスト、responseはローカルステート(useState)に格納しました。axiosの部分を上記sanityClientに置き換えて頂ければ理解できると思います。今日も最後まで読んで頂きありがとうございました。