概要
こんにちは。HRBrainでオウンドメディア・ランディングページの開発を担当している渡邉です。
HRBrainでは、Next.js(TypeScript)とContentfulというHeadless CMSを使ってオウンドメディアを運営しています。
本記事では、Contentfulに入力したRich TextデータをNext.js側からAPI経由で取得し、目次を作成する方法をご紹介します。
手順
- ContentfulのAPIを使用してRich Textデータを取得(本記事では具体的なAPIリクエストは省略します)
- 必要なライブラリを追加
- Rich TextデータをHTMLに変換
- Rich Textデータから目次を生成
- コンポーネント側から呼び出す
必要なライブラリの追加
最初に、実装で必要となるContentful関連のライブラリをインストールします。
npm install @contentful/rich-text-types @contentful/rich-text-react-renderer
▼ContentfulのRich Textデータ用型ファイル
▼ContentfulのRich TextデータをReactコンポーネントに変換するライブラリ
Rich TextをHTMLに変換
Contentfulから取得したRich Textデータは、以下のようにnodeデータが配列で提供されます。
このRich Textデータを@contentful/rich-text-react-renderer
ライブラリのdocumentToReactComponents
関数を使用してHTMLに変換します。
また、目次の項目をクリックした際にページ内リンクでジャンプできるように、h2タグに対して#heading-1
#heading-2
という形式のID属性を付与します。
import { BLOCKS, Document } from '@contentful/rich-text-types'
import { documentToReactComponents } from '@contentful/rich-text-react-renderer'
type Props = {
document: Document
}
export default function ArticleBody({ document }: Props): React.ReactElement {
let headingIndex = 0
const options = {
renderNode: {
[BLOCKS.HEADING_2]: (node) => <h2 id={`heading-${++headingIndex}`}>{node.content[0].value}</h2>
[BLOCKS.HEADING_3]: (node) => <h3>{node.content[0].value}</h3>
[BLOCKS.HEADING_4]: (node) => <h4>{node.content[0].value}</h4>
[BLOCKS.HEADING_5]: (node) => <h5>{node.content[0].value}</h5>
}
}
return <div class="post-content">{documentToReactComponents(document, options)}</div>
}
Rich Textから目次を生成
Propsのdocument
にRich Textが渡されるので、このデータを整形してulタグとして出力します。
- Rich Textからh2タグ(
heading-2
)をfilterで取得 - mapを使用してデータを整形
- アンカーリンクはページ内リンクになるように、ID属性へのリンクを付与
import { Text, Document } from '@contentful/rich-text-types'
type Props = {
document: Document
}
export default function TableOfContents({ document }: Props): React.ReactElement {
const headings = document.content
.filter((content) => content.nodeType === 'heading-2')
.map((content, index) => ({
id: `heading-${++index}`,
text: (content.content[0] as Text).value,
}))
return (
<ul>
{headings.map((heading) => (
<li key={heading.id}>
<a href={`#${heading.id}`}>{heading.text}</a>
</li>
))}
</ul>
)
}
記事側で各コンポーネントを呼び出す
必要な箇所で目次と記事本文を呼び出します。
以下の例では、目次の後に記事本文が配置されるようにしています。
import { Document } from '@contentful/rich-text-types'
import TableOfContents from '@/components/TableOfContents'
import ArticleBody from '@/components/ArticleBody'
type Props = {
post: {
fields: {
rich_text: Document
}
}
}
export default function Article({ post }: Props): React.ReactElement {
return (
<article>
<TableOfContents document={post.fields.rich_text} />
<ArticleBody document={post.fields.rich_text} />
</article>
)
}
おまけ: ページ内リンクジャンプ後に見出しがheaderに隠れる場合
ページ内リンクでジャンプした後、見出しがheaderと重なる問題が発生する場合があります。
これを解決するために、遷移先の見出しに対してscroll-margin-top
プロパティを指定します。
指定した値だけ、ズレた状態で見出しにジャンプすることができます。
h2 {
scroll-margin-top: 150px;
}
ContentfulからRich Textデータを取得し、目次を生成するまでの順序を説明しました。
実装する際の参考にしてください。
PR
HRBrainではフロントエンドエンジニア(コミュニケーションデザイン)の採用も行なっています。