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"
    }
}
    
こうすることで、型定義が@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>
    )
}
    
これで、post一覧ページと個別ページの作成は完了です。

まとめ

以上のように、Next.jsとContentfulを用いてブログ機能を実装する方法を紹介しました。

弊社の「おしらせ」ページおよび「技術発信」ページも、同様の方法で実装しています。みなさまも、ぜひお試しください。