VercelのNextraブログサンプルに感化され、Next.jsブログにタグ機能を自作した話【#作成方法まとめ】

2022-05-12
Main Image

目次

こんにちは。

最近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 portfolio

めっちゃシンプルでクールですね。

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.tsgray-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内で、そのページのPostDataTagをすべて表示させます。記事マークダウンに書くのは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を使って各タグの一覧ページをビルド時に生成させます。getStaticPathspages配下でしか使えませんので、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版もあります。日本語の解説書があるのは嬉しいですね。

React.js&Next.js超入門 第2版

ads【オススメ】未経験からプログラマーへ転職できる【GEEK JOBキャンプ】
▼ Amazonオススメ商品
ディスプレイライト デスクライト BenQ ScreenBar モニター掛け式
スマートLEDフロアライト 間接照明 Alexa/Google Home対応

Author

Penta

都内で働くITエンジニアもどき。好きなものは音楽・健康・貯金・シンプルでミニマルな暮らし。AWSクラウドやデータサイエンスを勉強中。学んだことや体験談をのんびり書いてます。TypeScript / Next.js / React / Python / AWS / インデックス投資 / 高配当株投資 More profile

Location : Tokyo, JPN

Contact : Twitter@penguinchord

Recommended Posts

Copy Right / Penguin Chord, ペンギンコード (penguinchord.com) 2022 / Twitter@penguinchord