Next.js + contentlayerで作ったブログにタグクラウドを実装する
2023/08/29

Next.js + contentlayerで作ったブログにタグクラウドを実装する

4 mins to read

今回はlodashを使ってタグクラウドを実装していきます

MDXファイル

このブログの記事のmdxファイルのフロントマター部分はこういう感じになっています

---
title: Next.js + contentlayerで作ったブログにタグクラウドを実装する
date: 2023-8-29
tags:
  - nextjs
  - contentlayer
---

parseされた後は、['nextjs', 'contentlayer']という感じの配列になります

タグデータの取得(getAllTags)

getAllTags.ts
import { Post, allPosts } from "@/.contentlayer/generated";
import _ from "lodash";

export interface Tag {
  slug: string;
  count: number;
}

export const getAllTags = () => {
  const tags: { slug: string }[] = [];
  allPosts.map((p: Post) => {
    p.tags?.map((t: string) => {
      tags.push({ slug: t });
    });
  });
  const res: Tag[] = _(tags)
    .groupBy("slug")
    .mapValues("length")
    .toPairs()
    .map(([slug, count]: Tag[]) => ({ slug, count }))
    .orderBy("count", "desc")
    .value();
  return res;
};

一旦記事を全件取得し、記事に紐づいたタグ全てをtags配列に入れてから、lodashを使って重複した回数とタグ名を配列に格納することで、タグに紐づいた記事数を取得しています。

translateTagName関数

例えば「Next.js」というタグをつけたい場合、Next.jsをそのままURLに使うとURLが「https://xxxx.com/tags/Next.js」という風になってしまい、jsファイルみたいに見えるので見栄えが良くないですよね。

なので引数に文字列「nextjs」を渡したら、文字列「Next.js」を返す関数を作ります。

translateTagName.ts
export const translateTagName = (slug: string): string => {
  const ts = [
    { from: "nextjs", to: "Next.js" },
    { from: "react", to: "React" },
    { from: "prisma", to: "Prisma" },
    { from: "tailwindcss", to: "TailwindCSS" },
    { from: "planetscale", to: "PlanetScale" },
    { from: "javascript", to: "JavaScript" },
    { from: "typescript", to: "TypeScript" },
  ];

  let str = slug.toLocaleLowerCase();
  ts.map((e) => {
    if (e.from === str) str = e.to;
  });
  return str;
};

使いたいタグが増えたら、ここを増やして対応してください

タグクラウドコンポーネント

getAllTagsで取得したタグ一覧をループさせ、/tags/${tag.slug}へのリンクを作っていきます。これでできあがりです🥳

import { getAllTags } from "@/lib/getAllTags";
import { translateTagName } from "@/lib/translateTagName";
import Link from "next/link";

export const TagCloud = () => {
  const tags = getAllTags();
  return (
    <div className="relative w-[100vw]">
      <div className="flex w-[100%] items-center justify-center bg-muted pb-8 pt-8 sm:pb-12 sm:pt-10">
        <div className="inner flex w-base flex-col items-center justify-center">
          <h3 className="mb-5 text-3xl font-black">TAGS</h3>
          <div className="flex flex-wrap justify-center gap-1.5 sm:gap-2">
            {tags.map((tag: Tag) => {
              return (
                <Link
                  key={tag.slug}
                  href={`/tags/${tag.slug}`}
                  className="flex select-none items-center gap-1 rounded-md border border-[rgba(var(--foreground),0.15)] px-4 py-1 text-sm font-bold duration-100 hover:border-[rgba(var(--foreground),0.45)]"
                >
                  <span>{translateTagName(tag.slug)}</span>
                  <span className="text-xs opacity-70">({tag.count})</span>
                </Link>
              );
            })}
          </div>
        </div>
      </div>
    </div>
  );
};

上記の例では色の設定にCSS変数などを使っているのでそのままだとスタイルが適用されませんが、色の指定部分を変更すれば動くと思います。

このブログの下にあるタグ一覧が完成品になります。