VercelのNextraブログサンプルに感化され、Next.jsブログにタグ機能を自作した話【#作成方法まとめ】
目次
こんにちは。
最近NextraとNext.jsで作成されたブログサンプルを見つけてしまい(今更)、あまりのシンプルさと完成度の高さに感動してしまいました。
とはいえいまさら乗り換えるのも面倒なので、ブログサンプルで特に良いなと感じた「タグ機能」を自己流で実装してみることにしました。
本記事ではその方法をまとめておきます。
Nextraのブログサンプルとは?
Nextra https://nextra.vercel.app/ はNext.jsをベースに開発された静的サイトを構築するライブラリです。MDXとよばれるMarkdown(.md
ファイル)の中にReactやJSXが書けるファイル形式をサポートしており、非常にシンプルな構成で静的サイトのページ作成ができます。ReactとJSXについてはこちらの記事でわかりやすく紹介しています。
で、Nextraを使ったNext.jsのブログサンプル"Portfolio"はこちら。 https://demo.vercel.blog/
めっちゃシンプルでクールですね。
Nextraのブログを作る方法
使用方法は簡単で、以下のコマンドで引っ張ってくるだけ。
npx create-next-app --example blog my-blog
# or
yarn create next-app --example blog my-blog
# or
pnpm create next-app -- --example blog my-blog
上のリンクと同じブログのテンプレートがダウンロードでき、Vercelにデプロイするだけでかっこいいブログサイトが作れます。簡単すぎて多分10分くらいで作れそう。あとはマークダウンを書き換えて記事を作っていくだけ。すごい。
参考 Github : https://github.com/vercel/next.js/tree/canary/examples/blog
こんなタグ機能をブログに実装したい!
で、サンプルを見て「タグ機能がブログの閲覧性を高めるのに必要だよなー」と感じ、とはいえ今更テンプレートを乗り換えるのも面倒なので、今あるブログサイト(つまりこのブログ)にタグ機能を自作で追加することにしました。
タグ機能とは
タグ機能とは、ブログ記事ごとに「#TypeScript」「#AWS」などコンテンツのカテゴリを表すタグをつけて、クリックするとそのタグがつけられた記事一覧ページに飛ぶ機能です。
すでにこの記事にも実装してあるので試してみてください。PCで見ている方は、サイドバーにもタグ一覧がありますので、そこからもタグ付けされたページに飛べるようになっています。
タグ機能を設計する
まず、必要なタグ機能の要件をまとめました。
- 記事のマークダウンを書く際に、その記事のタグを書ける。数はいくつでも書ける。
- 記事ページにそのタグを「#タグ名」でアイコン表示する。
- タグアイコンをクリックするとそのタグがつけられた記事一覧ページに飛ぶ。
- タグの記事一覧ページはこんな感じ
- サイドバーにタグアイコンを一覧表示する。
よくあるブログの仕様と同じだと思います。特に工夫はないですが、よくあるということはユーザーが親しみやすいはず。
ふと思ったのですが、先頭に#
をつけた単語は、それが「(上記のような機能を持つ)タグである」と一般認知されてますよね。この文化を広めたのはTwitterでしょうか。
タグ機能の実装方法
というわけで上記の要件に沿ってプログラミングしていきます。
ちなみに当ブログはこちらの記事にあるように、これまたNext.jsのブログスターターキットをベースに作成されております。
以降の実装を詳しく知りたい方は、ブログスターターの中身(Github : https://github.com/vercel/next-learn/tree/master/basics/typescript-final)を合わせて見ることをおすすめします。
マークダウン記事にタグを書く
これは既存のブログスターターの中の機能ですでに実現できます。記事のマークダウンのヘッダにタグを書くだけです。
---
title: "記事のタイトル"
date: "2022-05-12"
tags: ["TypeScript","Next.js"]
---
こんな感じにしてみました。タグは複数書きたいのでリスト形式で。lib/posts.ts
でgray-matter
により解析・パースされます。
この部分です。
import matter from 'gray-matter'
...
const matterResult = matter(fileContents)
return {
id,
...matterResult.data
}
タグのpathと表示名をマッピングするオブジェクトを作る
タグ情報はライブラリに切り離します。URLにpathとして表示する文字列とページ上に表示するタグ名を分けたかったので、lib/tag.ts
にマッピングするオブジェクトを作成しました。型とオブジェクトをexport
することで他のファイルから読み込めるようにします。
export type Tag = { path: string, name:string }
export const Tags:Tag[] = [
{path: "aws", name: "AWS"},
{path: "ts", name:"TypeScript"},
...
]
このタグ情報をもとに、タグアイコンとリンクを作成する関数コンポーネントを作ります。
※ タグ一覧のURLパスとタグの表示名を同じ文字列に統一する場合は、このマッピングオブジェクトは不要です。もっとシンプルに実装できると思います。
タグアイコンのスタイリング
タグアイコンは横に複数並べたいのでdiv
要素はdisplay: "inline-block"
で。スタイルはアイコンっぽくborderRadius
で丸めに、背景と文字カラーは記事とコントラスト反転。
export const tagIconStyle = {
box:{
display:'inline-block'
},
text:{
padding: "0.5rem",
borderRadius:"0.5rem",
backgroundColor:"#333",
color:"#fff",
}
}
タグアイコンは関数コンポーネント化
getStaticPaths
で生成される記事ページ pages/posts/[id].tsx
に出力するタグアイコンのコンポーネントを追加します。
アイコンは何回も呼び出すので、こんな感じのJSX.Element
を出力する関数コンポーネントにしました。
const TagIcon = (tag:Tag, index:number) => (
<div style={tagIconStyle.box} key={index}>
<Link href={`/${tag.path}`}>
<a style={tagIconStyle.text}>
#{tag.name}
</a>
</Link>
</div>
)
ここでタグ名先頭に一律#
をつけます。
記事ページにタグアイコンを表示する
pages/posts/[id].tsx
内で、そのページのPostData
のTag
をすべて表示させます。記事マークダウンに書くのはpath
で、ページに表示するのはname
としたいので、オブジェクトを使って変換します。変換にはfilter
関数でpath
と記事のタグが一致するタグを検索します。複数タグがあるので、filter
で取得したタグリストの中身をmap
関数で取り出します。
import { Tags, Tag } from 'lib/tag'
// Tagオブジェクトから名前を検索して取得
const postTags = Tags.filter((t)=>(
postData.tags
?postData.tags.includes(t.path)
:false
))
return(
...
// 一致したタグをすべてアイコンとして出力
{postTags.map((pt)=>(TagIcon(pt)))}
...
)
私の場合、タグアイコンの関数コンポーネントを呼び出す場所は日付の下にしましたが、記事タイトルの上とかも良さげですね。お好みで。
同じタグ付けされた記事一覧ページを作る
記事一覧ページとはこういうページのことです。
記事ページpages/posts/[id].tsx
同様、getStaticPaths
を使って各タグの一覧ページをビルド時に生成させます。getStaticPaths
はpages
配下でしか使えませんので、pages/tag/[tag].tsx
というファイルを作り以下を書きます。
import { GetStaticPaths } from 'next'
export const getStaticPaths: GetStaticPaths = async () => {
return {
paths: tagPaths,
fallback: false
}
}
また、記事の一覧とリンクはpages/index.tsx
を真似して作りました。getStaticProps
から生成される静的コンテンツのプロパティをフェッチし、そのプロパティのタグ情報から、タグのpath
に一致するコンテンツのみを抽出します。
useRouter
で現在のパス = 一覧させる記事のタグ を取得- タグオブジェクトリストから
find
関数でパスをタグ名に変換 - 全記事リストから
filter
関数でタグ付けされた記事を抽出
// このページはどのタグのページか、ルートパスから取得
const route = useRouter().asPath.split("/")[1] // パスは"/[tag]" としたいので、"/"で分割した2番目をタグと認識させる
// タグオブジェクトでパスをタグ名に変換 (このページのタイトル等に使う)
const tag = Tags.find((t)=> (t.path===route))
// すべての記事から、タグが一致する記事のみ抽出
const taggedPosts = [...allPostsData.map((posts)=>(
posts.data.filter((p)=>p.tags.includes(tag.path))
))]
こんな感じで、このブログのタグ機能が実現できました。ほぼNext.jsのチュートリアルブログの知識だけで構築できました。
まとめ
というわけで今回はNextraのブログサンプルに感化されてNext.jsに自作でタグ機能を追加してみたので、その実装方法をまとめてみました。
ご参考になれば幸いです。
それではー
参考 : Next.jsの入門書がAmazonで購入可能です!Kindle版もあります。日本語の解説書があるのは嬉しいですね。