ブログにタグをつける
はじめに
今まで404ページになっていたBlog、Tagsのページをようやく実装できました。今回はその実装についての記事になります。過去のブログ作成に関しては、実装したばかりのブログ作成タグページをご参照ください。
タグをどのように表示するかいろいろ悩みましたが、本ブログの元となった tailwind-nextjs-starter-blog を参考にしました。初めてのブログの構築でも触れましたが、元サイトは自パッケージなどを使っているので実装を参考にしつつ、デザインは今のままとなるように今回は実装しました。本記事ではその実装方法について解説します。
Contentlayerでタグをつける
tailwind-nextjs-starter-blogおよび本ブログでは、markdownで書いた記事をContentlayerを使って実装しています。そのため、Contentlayerの設定を修正して、タグをつけていきます。
流れとしては以下の通りになります。
- 記事のメタデータにタグを追加する
- タグを集計する
- タグ一覧やタグ詳細ページを作る
記事のメタデータにタグを追加する
MarkdownまたはMDXファイルのメタデータをContentlayerで設定するためにはdefineDocumentType
を使用します。tailwind-nextjs-starter-blogでは過剰に設定しているため、本ブログではシンプルに下記のように設定した。
// contentlayer.config.ts
export const Post = defineDocumentType(() => ({
name: "Post",
filePathPattern: `posts/**/*.md`,
fields: {
title: { type: "string", required: true },
date: { type: "date", required: true },
tags: { type: "list", of: { type: 'string' }, default: [] },
summary: { type: "string" },
},
computedFields: {
...computedFields,
},
contentType: "markdown",
}));
filePathPattern
- 読み込むファイルパスを正規表現でで指定する
contentType
- MarkdownやMDXなど読み込むファイルの型式を指定する
fields
- 本文以外のメタ情報を定義できる
- 今回は
tags
という文字の配列を設定する - 定義の仕方は公式ドキュメント参照
computedFields
- 設定した
fileds
に対して動的に情報を設定できる
- 設定した
上記の設定に適用するように、Markdownファイルの先頭に下記のようなメタ情報を付与する。
<!-- 投稿記事のMarkdownファイルの先頭 -->
---
title: ブログにタグをつける
date: 2024-07-22
summary: 投稿記事ににタグをつけ、タグでフィルタリングして表示できるようにする。その実装のメモ
tags:
- ブログ作成
- Contentlayer
- Next.js
---
以上で投稿記事へのタグ付けは完了しました。これでpost.tag
と言った形で投稿記事からタグ情報を取得することができます。
タグを集計する
次に、タグ一覧やフィルタリングするためのタグリストの表示のために、投稿された記事のタグ情報を集計します。
集計にはmakeSourceを使います。
makdeSource
はビルド時のアクションを決めるもので、上で設定したdocumentType
もmakdeSource
内で指定して初めてContentlayerに認識してもらえます。
今回はmakeSource
内のonSuccess
にcreateTagCount
という関数を設定することで、ビルド成功時にcreateTagCount
関数を実行させます。
// contentlayer.config.ts
function createTagCount(allPosts) {
const tagCount: Record<string, number> = {}
allPosts.forEach((file) => {
if (file.tags) {
file.tags.forEach((tag) => {
const formattedTag = slug(tag)
if (formattedTag in tagCount) {
tagCount[formattedTag] += 1
} else {
tagCount[formattedTag] = 1
}
})
}
})
writeFileSync('./src/tagList.json', JSON.stringify(tagCount))
}
この関数では、タグ名をキー、値をカウント値とするレコードを使って集計を行い、JSONファイルへ出力しています。なお、github-slugger
パッケージによるslug
関数を使ってタグ名を補正します。これはURL化するときに不要となる空白や特殊文字、大文字小文字を調整するために使用していると思われます。
これで以下のようなJSONファイルを出力し、タグを集計することができました。
// tagList.json
{
"ブログ作成": 1,
"contentlayer": 1,
"nextjs": 1
}
タグ一覧やタグ詳細ページを作る
先ほど作成したリストをimportで読み込めば、タグ一覧を生成することができます。例えば、タグ詳細ページ作成のためのコードは下記の通りです。
import tagData from 'src/tagList.json'
export const generateStaticParams = async () => {
const tagCounts = tagData as Record<string, number>
const tagKeys = Object.keys(tagCounts)
const paths = tagKeys.map((tag) => ({
tag: encodeURI(tag),
}))
return paths
}
記載の通り好きな型で読み込めば、キー一覧を取得したり、その集計値を表示したりすることができます。ちなみに、空白や日本語でもURLパスになるようにコードの中ではエンコードしています。もちろんパスパラメータからタグ情報を取得するときはデコードが必要になります。
日本語タグが機能しなかった...
参考サイトの真似と、前述の学習を駆使して、タグ一覧やタグ詳細ページをうまく作れました。 これで終わり!と思って意気揚々とCloudflareへデプロイしましたが、日本語のタグページが404になってうまく表示されませんでした...
ローカルでの表示、対象タグページの生成はログからうまくできることがわかりました。そのため、おそらくエンコードしたURLがWAFか何かでブロックされてしまったと考えられます。
日本語のサイトでわかりやすさを重視すると、タグ名に日本語を設定するのは必須だと思います。回避策はいろいろとあるのかもしれませんが、今回はタグを、ラベル(日本語や特殊文字可)とリンク(URLに指定するもの)に分けることにしました。公式ドキュメントでオブジェクトのリスト型式ができることがわかったので、タグを以下のように設定しました。
// tagList.json
const Tag = defineNestedType(() => ({
name: 'Tag',
fields: {
label: { type: 'string', required: true },
link: { type: 'string', required: false }
}
}))
export const Post = defineDocumentType(() => ({
// 省略
fields: {
tags: { type: "list", of: Tag, default: [] },
// 省略
},
// 省略
}));
この場合、下記のようにmarkdownを設定すればラベルやリンクを設定できます。
<!-- 投稿記事のmarkdownファイルの先頭 -->
---
title: ブログにタグをつける
date: 2024-07-21
summary: 投稿記事ににタグをつけ、タグでフィルタリングして表示できるようにする。その実装のメモ
tags:
- label: ブログ作成
link: myblog
- label: Contentlayer
---
その後の集計やページ表示方法は本説明から割愛します。ご興味のある方はmyblogリポジトリをご参照ください。
おわりに
以上、タグをつける実装についての解説でした。想像以上にContentlayerの勉強になって非常に良い機会でした。
これでブログの見た目はほぼ整ってひと段落しました。今後は、まずブログ記事を増やしながら、
- 検索機能・ページネーション機能の追加(記事が増えた場合の見た目の改善)
- SEOの改善(見てくれる人を増やす)
- RSSの実装(見てくれる人への通知)
あたりに着手できたら良いなと考えています。興味を持って読んでくださる方がいれば幸いです。
最後まで読んでいただきありがとうございました。