Next.jsとContentfulで作るおしらせ機能
2023年12月8日
以前のウェブサイトもNext.js(バックエンドとしてWordPress)を使用していましたが、この度、株式会社登紀わの自社ホームページをリニューアルするにあたって、デザイン面・内容面だけではなく、ブログ機能やお問い合わせ機能についても見直しました。その取り組みを順次ご紹介します。
今回は、弊社の「おしらせ」ページをContentfulで実現したその方法をご紹介します。
Next.js採用の理由
TutorDirectなど、自社にて作成するウェブアプリケーションのフロントエンドのフレームワークとしてNext.js(React)を採用しており、その技術的な理解を深めるために、自社ページもNext.jsで作成することにしました。
このウェブサイトも、Next.js(14.0.3)で作成されています。ルーティングにはpages routerを使用しており、以下のNext.jsに関する説明でも同様とします。
Contentful採用の理由
リプレイス前のホームページでも、ニュース配信用にブログ機能を持たせており、そのブログ記事を管理するHeadlessCMSとしてWordPressを採用していました。また、ContactForm7を使用しており、お問い合わせフォームのバックエンドとしても使用していました。しかし、WordPressはHeadlessCMSとして用いるには機能が多いため、今回はブログ機能とお問い合せ機能を分割し、それらをわかりやすく管理しようと思い、HeadlessCMS、なかでもドキュメントや参考サイトが充実しているContentfulを採用しました。なお、Contentfulは、TutorDirectのブログ機能でも採用しています。
Contentfulでおしらせ(ブログ)を配信する
必要なライブラリをインストール
今回、ContentfulのContent Modelの型定義には contentful-typescript-codegen を使用します。また、Contentfulの公式のライブラリもあわせてインストールしましょう。
node
npm install contentful contentful-management contentful-typescript-codegen @contentful/rich-text-types
Content Management Tokenを発行
続いて型定義のためには、 Content Management Token の発行が必要です。 これは、Delivery API Access Tokenとは異なる ので注意が必要です。
Contentfulの管理画面にログインし、左上の「Settings」をクリックし、 CMA tokens メニューへ移動します。
Create personal access token ボタンを押下してtokenに名前をつけます。有効期限が求められますので、適宜設定してください。これを確認したら控えておいてください。
控えたアクセストークンは、.env.localに記述しておきます。.env.localに書かれた環境変数を利用するために、dotenvもインストールしておきます。
.env.local
CONTENTFUL_MANAGEMENT_API_ACCESS_TOKEN=xxxx
node
npm install dotenv
getContentfulEnvironment.jsを作成
projectのrootに getContentfulEnvironment.js を作成し、以下のように記述します。
getContentfulEnvironment.js
require("dotenv").config({ path: `.env.local` });
const contentfulManagement = require("contentful-management")
module.exports = function () {
const contentfulClient = contentfulManagement.createClient({
accessToken: process.env.CONTENTFUL_MANAGEMENT_API_ACCESS_TOKEN,
})
return contentfulClient
.getSpace(process.env.CONTENTFUL_SPACE_ID)
.then(space => space.getEnvironment(process.env.CONTENTFUL_ENVIRONMENT))
}
package.jsonに、次のようなnpm scriptを追加します。
package.json
{
"scripts": {
"contentful-typescript-codegen": "contentful-typescript-codegen --output @types/generated/contentful.d.ts"
}
}
node
npm run contentful-typescript-codegen
生成した型定義ファイルを確認
次のようなファイルが生成されているはずです。内容はご自身の作成したContent Modelのfieldsに合わせて変わります。
@types/generated/contentful.d.ts
import { Asset, Entry } from "contentful";
import { Document } from "@contentful/rich-text-types";
export interface IPostFields {
/** Title */
title: string;
/** Body */
body: Document;
/** Slug */
slug: string;
/** Excerpt */
excerpt: string;
}
/** Blog post */
export interface IPost extends Entry<IPostFields> {
sys: {
id: string;
type: string;
createdAt: string;
updatedAt: string;
locale: string;
contentType: {
sys: {
id: "post";
linkType: "ContentType";
type: "Link";
};
};
};
}
SSGでブログ記事を配信する
Next.jsの有用な機能の一つであるSSG(Static Site Generation)を用いて、ブログ記事を配信します。SSGとは、build時にContentfulからデータを取得し、静的なHTMLを生成する機能です。この機能を用いることで、ブログ記事の配信に必要なAPIリクエストを減らすことができます。
contentful clientの作成
contentfulから記事を取得するために、contentful clientを作成します。筆者は、次のように作成しました。
.env.local
CONTENTFUL_SPACE_ID = yyy
CONTENTFUL_ACCESS_KEY = zzz
utls/contentful.ts
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_KEY
})
以後、clientとして他の.tsxファイルから使用します。
post一覧ページの作成
まず、post一覧ページを作成します。pages/posts/index.tsxを作成しましょう。
getStaticPropsを実装
getStaticPropsを実装し、Contentfulからpostのデータを取得します。このとき、取得するデータの順番を指定することもできます。今回は、作成日時の降順で取得するようにしています。
pages/posts/index.tsx
export const getStaticProps: GetStaticProps = async () => {
const contentfulClient = getContentfulEnvironment()
const entries = await client.getEntries<IPostFields>({
content_type: "post",
order: "-sys.createdAt",
})
const posts = entries.items.map((entry) => entry.fields)
return {
props: {
posts,
},
}
}
PostListPageを実装
続いて、受けとったpostデータのstaticPropsをもとに、post一覧ページを作成します。pages/posts/index.tsxを以下のように編集します。
pages/posts/index.tsx
type Props = InferGetStaticPropsType<typeof getStaticProps>
export PostListPage:NextPage<Props> = ({posts}) => {
return (
<div>
<h1>おしらせ</h1>
<ul>
{posts.map((post) => (
<li key={post.sys.id}>
<Link href={`/posts/${post.slug}`}>
<a>{post.title}</a>
</Link>
</li>
))}
</ul>
</div>
)
}
post個別ページの作成
今回は、postの一意なslug fieldをもとに、post個別ページを作成します。pages/posts/[slug].tsxを作成しましょう。
getStaticPathsを実装
slugをもとに、postの個別ページを作成するためには、getStaticPathsを実装する必要があります
pages/posts/[slug].tsx
export const getStaticPaths:GetStaticPaths = async () => {
const res = await client.getEntries<IPostFields>({
content_type: "post",
})
const paths = res.items.map(
item => {
return { params: { slug: item.fields.slug } }
}
)
return {
paths,
fallback:false
}
getStaticPropsを実装
続いて、一覧取得したslugをもとに、対応する個別ページのデータを取得ための関数であるgetStaticPropsを実装します。pages/posts/[slug].tsx
export const getStaticProps:GetStaticProps = async ({params}) => {
const { items } = await client.getEntries<IPostFields>(
{
content_type: "post",
"fields.slug": params.slug,
}
)
return {
props:{
post: items[0].fields
}
}
個別ページの表示を実装
pages/posts/[slug].tsx
type Props = InferGetStaticPropsType<typeof getStaticProps>
const PostPage:NextPage<Props> = ({post}) => {
return (
<div>
<h1>{post.title}</h1>
<div>
{documentToReactComponents(post.body)}
</div>
</div>
)
}
まとめ
以上のように、Next.jsとContentfulを用いてブログ機能を実装する方法を紹介しました。
弊社の「おしらせ」ページおよび「技術発信」ページも、同様の方法で実装しています。みなさまも、ぜひお試しください。