ITプロパートナーズでエンジニアをしているもりと申します。
少し今更感がありますが、ContentfulとNuxtでJAM Stackなブログを作ってみました!
まずJAM Stackという聞き慣れない言葉がでてきましたが、JAM Stackとは
Javascript + Api + Markup のことで、HTMLをサーバーサイドで生成してレンダリングするのではなく、javaScriptでapiから取得したデータをもとにデプロイ時にHTMLを生成する最新のweb開発アーキテクチャのことです。
詳しくはこちら
今回はContentfulをAPIとして、Nuxtを静的サイトジェネレーターとして使用することで
JAM Stackとして実装していきます。
Contentful
- Contentfulアカウントの作成
- スペースの作成
- モデルの作成
- 記事の作成
Nuxt
- Nuxtアプリケーション作成
- Contentfulの設定
- Contentfulのコンテンツデータ取得
- ビルド時のhookと動的ルーティングの生成
- ブログページ
- 事前準備
- アカウント作成
Contentful
ContentfulとはヘッドレスCMSと呼ばれるコンテンツ管理サービスで、WordPressのように記事の作成や管理ができます。
WordPressと違うのは、Contentfull側でViewのレンダリングは行わず、完全にAPIベースである点です。
記事のデータはAPIを介して取得するため、フロントエンドは自分の好きな技術で好きなように作ることができます。
1. Contentfulアカウントの作成
まずはContentfulのアカウントを作成していきましょう。
Try for free とあるのでこちらからアカウントを作成します 。
今回はGithubと連携を行います。
名前やメールアドレスなどの必要事項を入力し signUp してアカウントの作成完了です。
2. スペースの作成
次に スペース という大枠になるものを作成します。
スペースとはContentfulの一番大きな領域で、1プロジェクト = 1スペースになります。
公式によると開発用とProduction用といった単位でスペースを作成することもありだそうです。
今回はブログを1つ作成するだけなので、スペースは1つでOKです。
早速スペースを作成していきましょう。
まずは左側にあるメニューからCreate spaceをクリックします。
space作成用のモーダルが表示されるので、最初にspaceのタイプを選択します。
今回はFreeプランで作成します。
次にスペース名を決めます。
今回はシンプルにblogとしておきました。
あとはConfirm & CreateSpaceをクリックしてスペースの作成完了です!
3. モデルの作成
次にモデルを作成していきましょう。
モデルとはContentfulで作成する記事などのコンテンツのことです。
例えば、 title top_image create_atのように、一つのコンテンツに持っておきたい情報を名前と型を決めて登録します。
今回はブログ記事なので、タイトルやTOP画像、作成日、本文などの情報を持つ記事モデルを作成してみたいと思います。
先程スペースを作成したので、TOPページに戻るとCreate spaceが Completedになっていると思います。
そのすぐ下にあるDefine the structure の Create a content type をクリックします。
モデルの名前と説明を入力しCreateをクリックしましょう。
今回はブログの記事なのでシンプルにarticleとしました。
次に、このモデルにフィールドを設定していきます。
作成後に右側にあるAdd Fieldをクリックします。
クリックすると追加するフィールドの型を決めるモーダルが表示されるので、Textを選択します。
フィールドの名前を入力してCreateします。
これでText型のブログのタイトルを表すフィールドの作成が完了です。
今回は合計5つのフィールドを作成しました。
これで、1つのコンテンツ(記事)はここで設定した5つのフィールドを持つことができるようになりました!
4. 記事の作成
スペース、モデルの作成が終わったので、実際にコンテンツ(記事)を作成してみましょう。
TOPページには、先程Modelも作成が終わったので、 Define the structureもCompletedになっているかと思います。
Create your contnet の Add an entry からコンテンツを作成していきます。
このように先程設定したフィールドの中身を入力していきましょう。
実際の本文はフィールドタイプをrichTextにしたので、このように文章の途中に自由に画像を差し込んだり、太字にしたり といったことが可能です。
記事の作成が終わったら、右上のpublishをクリックして作成完了です。
Nuxt
Contentfulでブログ記事の作成が完了したので、次はそのブログを表示するフロントエンドの部分の作成を行います。
今回はNuxtを使用してそのフロントエンド部分を作成していこうと思います。
NuxtとContentfulの連携については公式に手順が載っているのでこちらに沿って説明して行きたいと思います。
1. Nuxtアプリケーション作成
では早速Nuxtのアプリケーションを作成していきましょう。
まずはvue-cliを使ってNuxtの新規アプリケーションを作成します。
vue-cliのinstall
$ yarn global add @vue/cli-init
nuxtアプリケーションの作成
$ vue init nuxt/starter アプリケーション名
これでnuxtのアプリケーションが作成できたので早速Nuxtを起動してみましょう。
作成したNuxtアプリケーション配下に移動して必要なmoduleをinstallします。
$ yarn install
installが完了したら実際に起動してみます。
$ yarn dev
上記コマンドでbuildが開始され、URLが表示されるのでクリックしてみましょう。
成功していれば、このような画面が表示されます。
2. Contentfulの設定
Nuxtのアプリケーションの作成が完了したので、次はContentfulと連携するのに必要なModuleをinstallします。
$ yarn add contentful
contentfulと連携するにはapiのキーが必要になるのでそちらを作成しましょう。
作成が必要なのは
・space id
・access token
の2つです
contentfulのページのヘッダーにあるsettingsからAPIkeysページに移動します。
ページに移動すると、SpaceIDとAccessTokenなどが自動で生成されているのでこちらのキーを使用してcontentfulと連携をしていきます。
contentfulのキーの作成が終わったらそのキーを保存しておく場所をNuxt側に作ります。
プロジェクト直下にenv.jsを作成して以下のようにします。
// ./env.js
{
"CTF_SPACE_ID": "先程作成した Space ID",
"CTF_CDA_ACCESS_TOKEN": "先程作成したContent Delivery API - access token"
}
※こちらgitignoreに指定するのをお忘れなく
先程installしたcontentful用のmoduleを使うためにpluginを作成します
// ./plugins/contentful.js
const contentful = require('contentful')
const env = require('../env.js');
const config = {
space: process.env.CTF_SPACE_ID,
accessToken:process.env.CTF_CDA_ACCESS_TOKEN
}
// export `createClient` to use it in pagecomponents
module.exports = {
createClient () {
return contentful.createClient(config)
}
}
これでNuxtとcontentfulの連携の設定は完了です。
3. Contentfulのコンテンツデータ取得
連携の設定が完了したので、次はContentfulから先程書いた記事を取得します。
今回はJAM Stackにするので、buid時に全ての記事データを取得してjsonに固めます。
ではまずデータを取得するmoduleを作成しましょう。
modulesディレクトリ配下に、先程作成したcontentful連携用のpluginを使用して
データ取得する処理を書きます。
その後、取得したデータをjsonファイルとして指定ディレクトリに出力します。
// ./modules/createStaticJson.js
import { createClient } from '../plugins/contentful.js';
import env from '../env.js';
import fs from 'fs';
const outputPath = 'static/json/article.json';
/**
* Contentfulから取得したデータをjsonファイルとして出力します。
*/
export default async function outputStaticData() {
const client = createClient();
// contentfulからデータを取得
const articles = await client.getEntries({
'content_type': env.CTF_BLOG_POST_TYPE_ID,
order: '-sys.createdAt'
});
// jsonとして出力する
fs.writeFile(
outputPath,
JSON.stringify(articles),
err => {
if (err) {
throw err;
}
},
);
};
取得したjsonはstatic/json/article.jsonに配置するので、指定ディレクトリにはあらかじめ空のjsonファイルを作成しておきましょう。
// ./static/json/article.json
{}
これでデータの取得、jsonファイル出力処理は完了です!
4. ビルド時のhookと動的ルーティングの生成
データを取得する処理ができたので、その処理の呼び出し元を作っていきます。
今回は nuxt generateという nuxtアプリケーションを静的ファイルとして出力するコマンドを使用していきます。
そのため、このgenerateが実行されたタイミングでデータを取得するようにしましょう。
nuxt.config.jsのgenerateの中に以下のように処理を追加します。
まず、先程作成したデータを取得してjsonにする処理を呼びます。
// ./nuxt.config.js
import Articls from './static/json/article.json';
import outputStaticData from './modules/createStaticJson';
module.exports = {
generate: {
async routes() {
// contentfulからデータを取得してjsonにexportする
await outputStaticData();
// 取得したjsonからページを動的生成する
return Articls.items.map(i => {
return `articles/${i.fields.id}`;
});
}
},
// --- 以下省略 ----
その後に取得したデータから動的なページを生成します。
なぜこのような処理をする必要があるかというと
nuxt generateコマンドで静的ページとして出力した場合、
article/{id} のような動的URLのページは生成できないからです。
今回は
/ 記事一覧ページ
article/{記事ID} 記事詳細ページ
というページ構成で作成するので
contentfulから取得した記事IDをもとに記事詳細ページのroutingを追加しています。
5. ブログページ
最後に、実際に表示するページ部分を作成して行きます。
/ 記事一覧ページ
article/{記事ID} 記事詳細ページ
という構成にします
root /
├ pages/
├ index.vue
articles/
└ _id.vue
pages 配下の構成がそのままルーティングになるので、こんな感じでvueファイルを配置していきましょう。
ここではポイントだけ説明をしていきたいと思います。(vueの説明は省きます)
まず、取得したjsonの記事データを表示する方法ですが、jsonをimport してcomputedで参照するのが良いでしょう。
// ./pages/index.vue
const Articls = require('~/static/json/article.json');
export default {
computed: {
articles(){
return Articls.items;
}
}
}
詳細ページに関してはurlから対象の記事のデータを抽出します。
// ./pages/article/_id.vue
const Articls = require('~/static/json/article.json');
export default {
computed: {
article(){
const id = this.$router.params.id;
return Articls.items.find(i => i.fields.id == id);
}
}
// 以下省略
今回contentfulでrichテキストを使用して文章の途中に画像をいれたり、太文字にしたり
といったことを行っています。この richText を表示する部分は少し処理が必要です。
// ./pages/article/_id.vue
import { BLOCKS } from '@contentful/rich-text-types';
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
export default {
methods: {
toHtmlString(obj) {
const options = {
renderNode: {
[BLOCKS.EMBEDDED_ASSET]: ({ data: { target: { fields }}}) =>
`<img src="${fields.file.url}"/>`,
},
}
return documentToHtmlString(obj, options);
}
},
// 以下省略
このように変換するモジュールがライブラリにあるので、こちらを使用して変換の処理を行う必要があります。
template内ではv-htmlで指定し、変換メソッドを指定すればOKです。
// ./pages/article/_id.vue
<template>
<div v-html="toHtmlString(article.fields.body)"></div>
</template>
// 以下省略
Netlify
アプリケーションの実装が完了したので、次は実際にデプロイします。
今回はNetlifyという静的なサイトを無料でホスティングできるサービスを利用して
githubにpushしたら自動でデプロイされるようにします。
1. 事前準備
事前準備として、先程作成したアプリケーションをgithuにpushします。
$ yarn generate
nuxt generateで静的ファイルとしてビルドしましょう。
実行後にdistというディレクトリが作成されていると思います。
次に先程作成されたdistディレクトリをgit.ignoreから外します。
最後にgit pushでgithubにpushしたら準備完了です。
2. アカウント作成
次にhttps://app.netlify.com/signupからNetlifyのアカウントを作成しましょう。
今回はgithubと連携したいのでgithubアカウントでsignupします。
アカウント作成後のTOPページからgithubのリポジトリと連携していきます。
New site from Gitをクリックします。
GitHubを選択して認証します。
Only select repositoriesを選択肢、連携するgithubのリポジトリを選択します。
先程連携したリポジトリを選択肢します。
連携するリポジトリの設定を行います。
① pushがあった場合にhookするブランチを選択します。
今回は masterを選択しておきます。
② pushがあった場合に実行するコマンドを入れます。
コマンドは何も実行しないので未指定でOKです。
③ 実際にホスティングする対象となるディレクトリを入力します。
NuxtGenerateを行ったときのデフォルトディレクトリはdistなので今回はdistを指定してましょう。
最後に Deploy site でデプロイが開始されます 。
ビルドが成功するとこのようにURLが表示されます。
以上でデプロイ設定は完了です。
以降はコンテンツをcontentfulで更新したら、ローカルでnuxt generateを実行し
githubにpushすることで自動でデプロイができます。
これでJAMStackなブログが完成しました!!!
いかがだったでしょうか?
比較的お手軽に構築からデプロイまで出来たかと思います。
今回のブログは完全な静的サイトなので画像など最適化したりすれば阿部寛の速度も夢ではないかもしれません。(笑)
サンプルは以下に置いておくので参考にどうぞ
サンプルブログ
https://reverent-saha-5d7aec.netlify.com/
サンプルソースコード
https://github.com/frostnday/contentful_nuxt_sample