Contentful+Gatsbyアプリで記事のタグを表示するまで。

2020.10.13

CMSとしてContentfulを使って構築したアプリで、各記事にタグを付けたくなるケースがあると思います。

こういうやつ。

Contentfulでは各記事のタイトルやURLスラッグも自分でフィールドを定義する必要があり(いわばすべてWordpressのカスタムフィールドみたいなもの)、タグ付のような動的にキーワードを関連づける機能がないので、Gatsbyアプリで自作してみようと思います。

前提

  • Contentfulの会員登録が完了していて、すでに一般的な記事のContent modelが存在する
  • アプリ側をGatsby.jsで構築している

ざっくりとしたやり方

今回Content上では、タグを記事と同列のコンテンツ(Content)として扱う方法を採ります。

どちらもContentfulにContent modelとして登録します。Content同士(記事とタグ)はそれぞれ多対多で紐づけることができるので、その機能を利用してタグ付けを行う方針です。

Contentfulのレコード数を消費するちょっと面倒な方法に見えますが、タグごとの記事一覧ページを作成する際など、タグ自体にメタ情報(Slugや別名、画像など)が必要になるケースで柔軟に対応ができるようになります。また複数の記事執筆者にタグを新規追加してもらいたいようなケースにも最適です。

Contentful側の準備

Contentful側でTagのContent modelを作成、つまりTagを一記事のように扱えるようにします

タグモデルの作成

まず上部メニュー「Content model」から「Tags」という名前のContent modelを作成します。

とりあえず必要なフィールドは下記2つ。

  1. Title: タグ名として利用。「Text」タイプ(Short text)で登録
  2. Slug: タグのページを生成する際のURLスラッグに利用。「Text」タイプ(Short text)で登録

また、「Title」と「Slug」どちらもConfigureにて、「Required field」「Unique field」両方にチェックを入れてください(タグ一覧ページのURLがそれぞれ被らないようにするため)。

TagsのContent modelが下記のようになればOKです。

記事モデルの修正

次に、記事側でタグを紐づけられるフィールドを新規追加します。

Content fieldを新規作成し、「Rerference」タイプを選択すると、別のコンテンツを紐づけることが可能になります。 contentful screenshot

「Many references」を選択して、複数のTagを紐づけられるようにします。 contentful screenshot

「Validation」の設定にて、特定のContentのみを紐づけられるようにします。ここでは先ほど作成したContent modelの「Tags」を選択します。 contentful screenshot

これで記事モデル側の準備も完了です。下記のように、タグを紐づけたい記事のContent modelの下部にTagsが追加されていればOKです。

これで記事にタグを紐づけられるので、Contentfulの記事編集画面に遷移して、試しにタグ付けしてみるとこんな感じになります。

CMS上で複数のタグの紐付け、新規作成も簡単にできるようになりました。

Gatsby側の実装

まずGraphQLのクエリは下記のにします。

export const query = graphql`
    query BlogArticleQueryTop {
        allContentfulBlogArticle(filter: {node_locale: {eq: "ja-JP"}}) {
            edges {
                node {
                    id
                    title
                    slug
                    content {
                        content
                    }
                    thumbnail {
                        file {
                            url
                        }
                    }
                    tags {
                        title
                        slug
                    }
                    createdAt
                }
            }
        }
    }
`;

Contentfulのlocales設定で複数の言語を許容している場合のために、上記の例ではフィルタリング(filter: {node_locale: {eq: "ja-JP"}})を行って、記事が重複して取得されない様にしています。

Tagsの要素としてtitle(タグ表記名)とslug(タグのURL用)を取得しています。

で、記事表示部分。今回は記事の一覧ページでの表示を想定しているので、map()関数で記事の配列を展開していて、その中でさらにTagsの配列を展開する形にしています。

const PostBasic = ({ postData }) => (
  postData.map(({ node: post }) => (
  <div className="post-basic-item">
    <img src={post.thumbnail.file.url} alt="Slide1" className="thumbnail" />
    <div className="post-basic-textblock">
      <p className="post-basic-postedat">{post.createdAt}</p>
      <h3><Link to={`/post/${post.slug}`}>{post.title}</Link></h3>
      <p>{post.content.content}</p>
      <div className="post-basic-catbox">
        <span className="post-basic-catname">{post.category}</span>
        <ul className="post-basic-tags">
        {post.tags && post.tags.map(({ title, slug }) =>
          <li>{title}: {slug}</li>
          )
        }
        </ul>
      </div>
    </div>
  </div>
  ))
)

上記コードの中でタグ表示部分を抜き出してみます。

{post.tags && post.tags.map(({ title, slug }) =>
  <li><Link to={`/post/${slug}`}>#{title}</Link></li>
  )
}

記事によってはタグを持たないケースがあるので、論理演算子( aaa && bbb )によって、要素(この場合はpost.tagsを指す)がnullだったら配列の展開処理(map)を実行しないようにしています。基本的なことなのですが、最初は記事がタグを持たないケースがあることに気がつかずハマってしまいました・・・。

フロントでは下記のようになります。成功!

 

最後に

ContentfulはCMS側のセキュリティの気苦労を大幅に減らしてくれ、他のヘッドレスCMSと比較してカスタマイズ性も高い一方、タグ付、人気記事、関連記事などの機能は自前で実装する必要があります。

まだまだ日本語記事が少ない印象なので、今後もマイペースにブログで更新していきます。