Blog

同じタグの記事一覧を作る

Cover Image for 同じタグの記事一覧を作る

タグが無いブログがあるだってぇ

このブログで使用している、最プなNext.jsテンプレート(※)では、記事カテゴリやタグという概念がありません。とりあえず、タグと、同じタグの記事一覧ページがあったら便利だなーっていうわけで、実装しました。

※「最もフィジカルで、最もプリミティブで、最も安上がりなテンプレート」の略

gray-matterにタグを追加

このテンプレートでは、gray-matterというライブラリでMarkdownファイルに書かれたメタ情報を読み取っているので、まずメタ情報にtagsという項目を追加しました。

---
title: "Next UIでモーダルを追加する"
excerpt: "画像クリックで拡大表示するモーダル、それはフロント音痴の永遠の憧れ。Next UIが授けてくれた希望、そして、use clientの闇とは..."
coverImage: "/assets/cover_images/20250104.png"
date: "2025-01-04T00:00:00"
author:
  name: Sachison Nitta
  picture: "/assets/blog/authors/jj.jpeg"
ogImage:
  url: "/assets/cover_images/20250104.png"
tags:
  - Next.js
  - Next UI
---

これでメタ情報を取得している部分(src/lib/api.tsmatter()している箇所)で他のメタ情報と一緒にタグが配列で取得されます。

タグを記事のヘッダーに表示

タグはとりあえず記事のヘッダーで表示したいので、関連するコンポーネントを編集。

posts/[slug]/page.tsx

...
export default async function Post(props: Params) {
  ...
  return(<main>
      <Alert preview={post.preview} />
      <Container>
        <Header />
        <article className="mb-32">
          <PostHeader
            title={post.title}
            tags={post.tags}
            coverImage={post.coverImage}
            date={post.date}
          />
          <PostBody content={content} />
        </article>
      </Container>
    </main>
  );
}
...

tag/[slug]/page.tsx

import CoverImage from "./cover-image";
import DateFormatter from "./date-formatter";
import { PostTitle } from "@/app/_components/post-title";
import Link from "next/link";
import slugify from 'slugify';

type Props = {
  title: string;
  tags: Array<string>;
  coverImage: string;
  date: string;
};

export function PostHeader({ title, tags, coverImage, date }: Props) {
  return (
    <>
      <PostTitle>{title}</PostTitle>
      <div>
        <ul className="flex gap-4 mb-8">
        {tags.map((item) => {
          const slungified = slugify(item, {
            lower: true,
            strict: true,
          });
          return (<li className="border border-gray-400 rounded-lg p-4"><Link href={`/tag/${slungified}`}>{item}</Link></li>);
        })}
        </ul>
      </div>
      <div className="mb-8 md:mb-16 sm:mx-0">
        <CoverImage title={title} src={coverImage} />
      </div>
      <div className="max-w-2xl mx-auto">
        <div className="mb-6 text-lg">
          <DateFormatter dateString={date} />
        </div>
      </div>
    </>
  );
}

slungifyは文字列を「Next UI」→「next-ui」のようにURLとして読みやすい形に変換してくれるライブラリです。タグをクリックしたらtags/[slug]/page.tsxに遷移して、同じタグを使っている記事一覧を表示させたいのですが、そのままだとURLが「/tags/Next%20UI」になっちゃうので「/tags/next-ui」になるように変換しています。

(追記: slungify、日本語の文字列は空文字になってしまうことが発覚。そのうち日本語対応します...)

tags/[slug]/page.tsxも適当に作って、とりあえずタグをクリックしたら別ページに飛ぶようにしました。

記事のヘッダー。

遷移後の画面。

同じタグを使っている記事一覧を作成する

同じタグを使ってる記事一覧の取得処理は、全部の記事を取得→同じタグが入ってる記事をフィルタリングという流れにしました。記事が増えるとビルドが重くなりそうですがひとまずこれで行きます。

import { getAllPosts, getPostBySlug } from "@/lib/api";
import Alert from "@/app/_components/alert";
import Container from "@/app/_components/container";
import Header from "@/app/_components/header";
import slugify from 'slugify';
import { PostPreview } from "@/app/_components/post-preview";
import PageTitle from "@/app/_components/page-title";

type Params = {
  params: Promise<{
    slug: string;
  }>;
};

export default async function tag(props: Params) {
  const params = await props.params;
  const posts = getPostByTag(params.slug);
  return (
    <main>
      <Container>
        <Header />
        <PageTitle title="同じタグの記事一覧"/>
        <div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32">
        {posts.map((post) => (
          <PostPreview
            key={post.slug}
            title={post.title}
            coverImage={post.coverImage}
            date={post.date}
            slug={post.slug}
          />
        ))}
      </div>
      </Container>
    </main>
  );
}

export function getPostByTag(tag: string) {
  const posts = getAllPosts();
  return posts.filter((post) => post.tags.map((item) => slugify(item, {
    lower: true,
    strict: true,
  })).includes(tag));
}

タイトルが全ページ固定だったり、レイアウトがトップページの「もっと読む」の丸コピだったりで未完成ですが、記事一覧自体はなんとか表示できました。