自分のWebサイトでも作ってみるか
これまでに
楽天APIを叩いたレシピ検索サイト「今日の献立何にしよ?」
必要最低限の機能しかないくせにモダンなスキューモーフィズムを使っている電卓「ぷにぷに電卓」
計画から割と頑張って作ったチャットアプリ「CHA-CHAT-CHAT-APP」
と作ってきました。ここら辺で一つ自分のサイトをまとめる意味でも自分のサイトでも作ってみようかなとか思いました。
自分のサイト作成でもNext.jsを使用しようと思います。今度こそStaticGenerateやSSRといったNext.jsの機能を活かせると思います。さらに今回は今流行りのHeadless CMSも使用してみようかなと思います。
Headless CMS
CMSはその名の通り文章などのテキストや画像などのメディアを管理(マネージ)するサービスのことで有名なものだとワードプレスがあります。私はワードプレスを触ったことがあるのですが、ブログが書きたい訳でもショッピングサイトが作りたい訳でもなく少し触って飽きたので余り理解はしていません。ブログならテキストを管理する必要がありますし、ショッピングサイトなら商品の画像を管理する必要があります。ワードプレスにせよ何にせよコンテンツを管理する必要性が出てきた時に必要なのがCMSというわけですね。
Headlessとは表示する部分が無いという意味の単語です。ワードプレスだと文章にせよ画像にせよ編集・管理するページ(View)が存在します。Headless CMSはその編集・管理するページが存在せずCLIからコマンドで操作するタイプのCMSをHeadless CMSといいます。
Headless CMSのサービスはいろいろ存在しますが、今回はContentfulというサービスを利用しようと思います。
これから先のプロジェクト作成手順は以下のサイトを参考にやっていきます。全て英語ですが雰囲気で読んでいきます。適当です。
Contentfulのセッティング
サイトには書かれていませんがまず登録をしましょう。海外のサービスなのでこれまた全て英語ですがそれっぽいものを選んでいきます。「Get started」から以下のページに飛びます。何やら4つほど選択肢があります。イラスト的にはI BUILD SOFTWAREが一番あってそうなのでこれのTry for freeにします。収益化を望んでいる訳では無いのでお金は発生させたくありません。
次にサインアップ (登録)処理に写ります。githubとgoogleのアカウントで登録出来るようです。vercelへはgithubからデプロイするのでgithubで登録します。この後名前とか会社とか聞かれますが適当に入力してください。
登録が終了するとダッシュボードに遷移します。ここでコンテンツのモデルやAPI Keyの生成を行う「Space」を作成します。サンプルのspaceが用意されていていろいろなモデルがすでに入っていますので、消して新しいのを作成します。
spaceを作成したらcontent modelを定義しましょう。複数定義出来るようなのでまず自分が作ったWebアプリを管理するモデルを作ります。これから作るサイトで紹介する際に必要になる情報をフィールドとして作成します(以下の画像参照)。
次にデータをいくつかセットしましょう。今回は冒頭であげている自分が作ったアプリの情報をセットします。上の画像のメニューバーにあるContentからデータをセットすることが出来ます。[Add content model]を押すと以下のような画面が開くので必要な情報を入力していきましょう。
あとはsettingsからAPI keyを生成すればContentfulでの作業は終了です。
Create nextjs app
次にNext.jsのプロジェクトを作ります。create-next-appではなくnpmで必要なモジュールをダウンロードしていくみたいです。私の場合は
$ mkdir my-website-nextjs-contentful && cd my-website-nextjs-contentful
$ npm init -y
$ npm i contentful next react react-dom
これで次のようなディレクトリが作成されます。
あとtypescriptで書きたいのでタイプスクリプトも入れます。なんか静的派からは中途半端、動的派からは何の意味があるの?とディスられ可哀想な子になっているみたいですが、個人的には型を意識するようになってだいぶコードが書きやすくなったので気に入って使っています。あとtsconfig.jsonも忘れず作っておきましょう。
npm install --save-dev typescript @types/react @types/node
次にContentfulで生成したAPI KeyをセットしてContentfulのデータを読み込めるようにしましょう。ルートに.env.localを作成し生成したAPI Keyを貼り付けます。
.env.local
NEXT_PUBLIC_CONTENTFUL_SPACE_ID=your contentful space id
NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN=your contentful access token
これはローカルホストで実行する際に使用するものだと思われます。一般的.envで設定したからといってホスティングした先にもAPI keyの設定が反映される訳ではありません。vercelの場合以下のようにダッシュボードから入力することも出来ます。
たいていのホスティングサービスにはCLIやファイルから設定出来る機能がついています。vercelの場合ルートにvercel.jsonを作るとデプロイ時に反映されると思われます。(今度デプロイした時に確認してみます)。
追記
以下にデプロイした時のことを記事にしています。こちらの記事を参考にしてください。
vercel.json
{
"build": {
"env": {
"NEXT_PUBLIC_CONTENTFUL_SPACE_ID": "@contentful_space_id",
"NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN": "@contentful_access_token"
}
}
}
ここからpagesディレクトリを作りその中にreactファイルを作っていくのですが、記事と全く一緒ではなく確認しながらやっていきます。pages/index.tsxとpages/_app.tsxを作成し以下を記述します。
pages/_app.tsx
import { AppProps } from "next/app";
function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default App;
pages/index.tsx
import { useEffect } from "react";
import Head from "next/head";
const Contentful = require("contentful");
const client = Contentful.createClient({
space: process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID,
accessToken: process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN
});
function Index() {
async function fetchEntries() {
const entries = await client.getEntries();
console.log(entries);
}
useEffect(() => {
(async() => {
await fetchEntries();
})();
}, []);
return (
<div>
<Head>
<title>bkc</title>
</Head>
Home
</div>
);
}
export default Index;
一つ注意としてはcontentfulはimport文ではなくrequire文でしか読み込めなかったという点です。ドキュメント調べたらimport文での書き方も出てくるのかな?
それと今回はまずどんな形でデータが送られてくるか確認するためにまずconsole.log()で受け取ったデータを表示します。
送られてきたデータの形が何となくわかったので、これをステートの中に打ち込むところまでやろうと思います。index.tsxを以下のように書き換えます。
index.tsx
import { useState, useEffect } from "react";
import Head from "next/head";
const Contentful = require("contentful");
const client = Contentful.createClient({
space: process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID,
accessToken: process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN
});
function Index() {
const [items, setItems] = useState([]);
async function fetchEntries() {
const entries = await client.getEntries();
return entries.items;
}
const setFetchToState = (fetch:any) => {
setItems(fetch);
}
useEffect(() => {
(async() => {
const items = await fetchEntries();
setFetchToState(items);
})();
}, []);
return (
<div>
<Head>
<title>bkc</title>
</Head>
{ items.map((item, id) => {
return(
<div key={ id }>
<a href={ item.fields.siteUrl }>{ item.fields.title }</a>
</div>
);
})}
</div>
);
}
export default Index;
取ってきたデータをステートに代入してコンポーネントで表示するようにしただけです。これを実行すると、
良さそうですね。これで基本的なことは出来ると思うので自分のサイト作成をやっていこうと思います。