Next.jsのブログに前後記事へのリンクを追加する方法
2021.12.252023.6.7
この記事は約3分で読めます
この記事の筆者:三好アキ
🔹 専門用語なしでプログラミングを教えるメソッドに定評があり、1200人以上のビギナーを、最新のフロントエンド開発入門に成功させる。
🔹 Amazonベストセラー1位を複数回獲得している『はじめてつくるReactアプリ with TypeScript』著者。
Amazon著者ページはこちら → amazon.co.jp/stores/author/B099Z51QF2
React、Next.js、TypeScriptなどのお役立ち情報や実践的コンテンツを、ビギナー向けにかみ砕いて無料配信中。登録はこちらから → 無料メルマガ登録
前後の記事へのリンク
本記事ではまず最初にNext.jsにマークダウンのブログ機能を実装し、その後前後の記事へのリンクを作ります。
ロジック部分の説明だけなので、スタイルは各自お好みで適用してください。
なおページネーション機能は、こちらの記事を参考にしてください。
Next.jsバージョン13で導入されたAppフォルダを使った最新のNext.js開発に興味のある方は、今月(2023年6月)リリースした下記書籍を参考にしてください。
マークダウンブログ機能の作成
まずNext.jsにマークダウンのブログ機能を追加します。
内容は下の記事で解説したものなので、すでに読んでいる人はパスしてください。
create-next-app
をダウンロードします。
npx create-next-app nextjs-blog
記事を一覧表示するページとなるblog.js
、そして記事データのマークダウンファイルを収納するフォルダdata
を作ります。
nextjs-blog
├── data ←追加
├── pages
│ ├── api
│ │ └── hello.js
│ │
│ ├── _app.js
│ ├── blog.js ←追加
│ └── index.js
│
次に、data
の中にブログ記事のファイルを作ります。
├── data
│ ├── first.md
│ ├── second.md
│ ├── third.md
│ ├── fourth.md
│ ├── fifth.md
│ └── sixth.md
それぞれのマークダウンファイルには、下のように基本的な情報を書きます。
// first.md
---
id: "1"
title: "記事1"
date: "2021-06-21"
summary: "記事1の要約"
---
1つ目の記事。
// second.md
---
id: "2"
title: "記事2"
date: "2021-06-22"
summary: "記事2の要約"
---
2つ目の記事。
// third.md
---
id: "3"
title: "記事3"
date: "2021-06-23"
summary: "記事3の要約"
---
3つ目の記事。
// fourth.md
---
id: "4"
title: "記事4"
date: "2021-06-24"
summary: "記事4の要約"
---
4つ目の記事。
// fifth.md
---
id: "5"
title: "記事5"
date: "2021-06-25"
summary: "記事5の要約"
---
5つ目の記事。
// sixth.md
---
id: "6"
title: "記事6"
date: "2021-06-26"
summary: "記事6の要約"
---
6つ目の記事。
次にNext.jsでマークダウンファイルを扱うためのパッケージもダウンロードしておきます。
npm install raw-loader gray-matter react-markdown
トップレベル(ルート)のnext.config.js
に下のコードを書きます。
// next.config.js
module.exports = {
webpack: function (config) {
config.module.rules.push({
test: /\.md$/,
use: "raw-loader",
})
return config
},
}
blog.js
を開き、次のコードを追加します。
// blog.js
import Link from 'next/link'
import matter from "gray-matter"
const Blog = (props) => {
return (
<>
<p>ブログ一覧ページ</p>
{props.blogs.map((blog, index) => (
<div key={index}>
<h2>{blog.frontmatter.title}</h2>
<Link href={`/blog/${blog.slug}`}><a>Read More</a></Link>
</div>
))}
</>
)
}
export default Blog
export async function getStaticProps() {
const blogs = ((context) => {
const keys = context.keys()
const values = keys.map(context)
const data = keys.map((key, index) => {
let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
const value = values[index]
const document = matter(value.default)
return {
frontmatter: document.data,
slug: slug
}
})
return data
})(require.context('../data', true, /\.md$/))
const sortingArticles = blogs.sort((a, b) => {
return b.frontmatter.id - a.frontmatter.id
})
return {
props: {
blogs: JSON.parse(JSON.stringify(sortingArticles))
}
}
}
個別の記事ページでは、まずテンプレートとして使うファイルを次のように作ります。
├── pages
│ ├── api
│ ├── blog ←作成
│ │ └── [slug].js ←作成
│ ├── _app.js
│ ├── blog.js
│ └── index.js
そして次のコードを書きます。
// [slug].js
import matter from "gray-matter"
import ReactMarkdown from 'react-markdown'
const SingleBlog = (props) => {
return (
<div>
<h1>{props.frontmatter.title}</h1>
<p>{props.frontmatter.date}</p>
<ReactMarkdown>
{props.markdownBody}
</ReactMarkdown>
</div>
)
}
export default SingleBlog
export async function getStaticPaths() {
const blogSlugs = ((context) => {
const keys = context.keys()
const data = keys.map((key, index) => {
let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
return slug
})
return data
})(require.context('../../data', true, /\.md$/))
const paths = blogSlugs.map((blogSlug) => `/blog/${blogSlug}`)
return {
paths: paths,
fallback: false,
}
}
export async function getStaticProps(context) {
const { slug } = context.params
const data = await import(`../../data/${slug}.md`)
const singleDocument = matter(data.default)
return {
props: {
frontmatter: singleDocument.data,
markdownBody: singleDocument.content,
}
}
}
以上で、Next.jsにマークダウンのブログ機能を作成することができました。
ステップ1(コードの整理)
getStaticProps
とgetStaticPaths
の整理を最初に行います。
utils
フォルダを作り、その中にmdQueries.js
ファイルを作成します。
nextjs-blog
├── data
.
.
.
├── styles
└── utils ← 作成
└── mdQueries.js ← 作成
mdQueries.js
にはgetStaticProps
とgetStaticPaths
で行なっていた処理を移動し、同じ働きをするコードの共有を効率化します。
次のコードを打ちます。blog.js
と[slug].js
のgetStaticProps
、getStaticPaths
のコードとほぼ同じですが、getSingleBlog
の'../data'
のパスが変わっていることに注意してください。
// mdQueries.js
import matter from 'gray-matter'
export async function getAllBlogs() {
const blogs = ((context) => {
const keys = context.keys()
const values = keys.map(context)
const data = keys.map((key, index) => {
let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
const value = values[index]
const document = matter(value.default)
return {
frontmatter: document.data,
slug: slug
}
})
return data
})(require.context('../data', true, /\.md$/))
const orderedBlogs = blogs.sort((a, b) => {
return b.frontmatter.id - a.frontmatter.id
})
return {
orderedBlogs: JSON.parse(JSON.stringify(orderedBlogs))
}
}
export async function getSingleBlog(context) {
const { slug } = context.params
const data = await import(`../data/${slug}.md`)
const singleDocument = matter(data.default)
return {
singleDocument: singleDocument
}
}
これらをblog.js
と[slug].js
で読み込んで使うので、それぞれ次のように修正します。
// blog.js
import Link from 'next/link'
import matter from "gray-matter" // 削除
import { getAllBlogs } from "../utils/mdQueries" // 追加
...
export default Blog
export async function getStaticProps() {
const { orderedBlogs } = await getAllBlogs() // 置き換え
return {
props: {
blogs: orderedBlogs // 置き換え
}
}
}
// [slug].js
import matter from "gray-matter" // 削除
import ReactMarkdown from 'react-markdown'
import { getAllBlogs, getSingleBlog } from "../../utils/mdQueries" // 追加
...
export default SingleBlog
export async function getStaticPaths() {
const { orderedBlogs } = await getAllBlogs() // 置き換え
const paths = orderedBlogs.map((orderedBlog) => `/blog/${orderedBlog.slug}`) // 修正
return {
paths: paths,
fallback: false,
}
}
export async function getStaticProps(context) {
const { singleDocument } = await getSingleBlog(context) // 置き換え
return {
props: {
frontmatter: singleDocument.data,
markdownBody: singleDocument.content,
}
}
}
ステップ2(前後の記事の指定)
前後の記事へ移動するには、現在のページの一つ前と一つ後のページを特定する必要があります。
そこで必要なのが各マークダウンファイルのfrontmatter部分のid
なので、まずマークダウンの全データを取得し、現在の記事のid
の一つ前と一つ後を選びます。
次のコードを追加します。
// [slug].js
...
export async function getStaticProps(context) {
const singleDocument = await getSingleBlog(context)
⬇追加
const { orderedBlogs } = await getAllBlogs()
const prev = orderedBlogs.filter(orderedBlog => orderedBlog.frontmatter.id === singleDocument.data.id - 1)
const next = orderedBlogs.filter(orderedBlog => orderedBlog.frontmatter.id === singleDocument.data.id + 1)
⬆追加
return {
props: {
frontmatter: singleDocument.data,
markdownBody: singleDocument.content,
prev, // 追加(prev: prevも可)
next, // 追加(next: nextも可)
}
}
}
これで前後のページのデータがprev
とnext
に格納されました。
ステップ3(コンポーネントの作成)
components
フォルダを作り、その中にprevNext.js
を作成します。
nextjs-blog
├── components ←追加
│ └── prevNext.js ←追加
├── data
.
.
.
そこに次のコードを打ちます。
// prevNext.js
import Link from 'next/link'
const PrevNext =(props) => {
return (
<div>
{0 < props.prev.length &&
<Link href={`/blog/${props.prev[0].slug}`}>
<a><h3> {props.prev[0].frontmatter.title}</h3></a>
</Link>
}
{0 < props.next.length &&
<Link href={`/blog/${props.next[0].slug}`}>
<a><h3>{props.next[0].frontmatter.title}</h3></a>
</Link>
}
</div>
)
}
export default PrevNext
これを[slug].js
で読み込み、props
でprev
とnext
を渡します。
[slug].js
ファイル全体は次のようになります。
// [slug].js
import matter from "gray-matter"
import ReactMarkdown from 'react-markdown'
const SingleBlog = (props) => {
return (
<div>
<h1>{props.frontmatter.title}</h1>
<p>{props.frontmatter.date}</p>
<ReactMarkdown>
{props.markdownBody}
</ReactMarkdown>
<PrevNext prev={prev} next={next} />
</div>
)
}
export default SingleBlog
export async function getStaticPaths() {
const { orderedBlogs } = await getAllBlogs()
const paths = orderedBlogs.map((orderedBlog) => `/blog/${orderedBlog.slug}`)
return {
paths: paths,
fallback: false,
}
}
export async function getStaticProps(context) {
const { singleDocument } = await getSingleBlog(context)
const { orderedBlogs } = await getAllBlogs()
const prev = orderedBlogs.filter(orderedBlog => orderedBlog.frontmatter.id === singleDocument.data.id - 1)
const next = orderedBlogs.filter(orderedBlog => orderedBlog.frontmatter.id === singleDocument.data.id + 1)
return {
props: {
frontmatter: singleDocument.data,
markdownBody: singleDocument.content,
prev,
next,
}
}
}
以上でNext.jsに前後の記事へのリンクを追加することができました。
本記事では細かい説明を省略して足早に解説しましたが、Next.jsについてよりくわしく知りたい方は、拙著「はじめてつくるNext.jsサイト」を参考にしてください。
Next.jsのページネーション機能は、こちらの記事を参考にしてください。
またNext.jsを活用して近年注目を集めるJamstackについて知りたい方は、次の記事を参考にしてください。
メルマガ配信中
(from 三好アキ/エンジニア)
React、Next.js、TypeScriptなど最新のウェブ開発のお役立ち情報を、ビギナー向けにかみ砕いて無料配信中。
(*配信はいつでも停止できます)