💡
【Next.js】Next.js & Contentful BlogApp 【13PostDetailsPage4】
【13PostDetailsPage4】
YouTube:https://youtu.be/2sacVyWRcGs
今回はブログ本文のスタイリングについて実装を進めていきます。
今回はライブラリの解説がメインになります。
こちらの記事からオプションファイルのコピーをお願い致します。
内容が難しいと感じた場合は、3:35までで実装が完了した後は
10:50まで説明を飛ばして、
最後の注意の部分だけ見て、次に進んでも大丈夫です。
まずは必要なライブラリを2つインストールから始めます。
カスタマイズの対象となる要素のタイプを取得するライブラリ
npm i @contentful/rich-text-types
コードの部分をエディター風にスタイリングするライブラリ
npm i react-syntax-highlighter
package.json
"dependencies": {
"@contentful/rich-text-types": "^15.14.1",
"react-syntax-highlighter": "^15.5.0"
},
一番上の階層に「lib」フォルダを作成して、
内部に「richTextOptions.js」を作成します。
今回はライブラリの解説をメインに進めますので、
まずはこちらのコードをすべてコピーしてください。
lib/richTextOptions.js
import { BLOCKS, MARKS } from '@contentful/rich-text-types'
import SyntaxHighlighter from 'react-syntax-highlighter'
import { monokaiSublime } from 'react-syntax-highlighter/dist/cjs/styles/hljs'
const CustomHeading_1 = ({ children }) => (
<h2 className="text-3xl font-black mb-2">{children}</h2>
)
const CustomHeading_2 = ({ children }) => (
<h3 className="text-2xl font-black mb-2">{children}</h3>
)
const CustomParagraph = ({ children }) => (
<p className="text-xl mb-1">{children}</p>
)
export const options = {
renderMark: {
[MARKS.CODE]: (text) => (
<SyntaxHighlighter
language="javascript"
style={monokaiSublime}
className="rounded-md text-sm"
>
{text}
</SyntaxHighlighter>
),
},
renderNode: {
[BLOCKS.HEADING_1]: (node, children) => (
<CustomHeading_1>{children}</CustomHeading_1>
),
[BLOCKS.HEADING_2]: (node, children) => (
<CustomHeading_2>{children}</CustomHeading_2>
),
[BLOCKS.PARAGRAPH]: (node, children) => {
if (
node.content.length === 1 &&
node.content[0].marks.find((x) => x.type === 'code')
) {
return <div>{children}</div>
}
return <CustomParagraph>{children}</CustomParagraph>
},
},
}
次にこちらのオプションを「[slug].js」でインポートして
「documentToReactComponents(item.fields.content)」の第2引数に設定します。
pages/posts/[slug].js
import Image from 'next/image'
import Layout from '../../components/Layout'
import { client } from '../../utils/contentfulClient'
import { documentToReactComponents } from '@contentful/rich-text-react-renderer'
import { options } from '../../lib/richTextOptions'
export const getStaticPaths = async () => {
const res = await client.getEntries({
content_type: 'myPosts',
})
const paths = res.items.map((item) => {
return {
params: { slug: item.fields.slug },
}
})
return {
paths,
fallback: 'blocking',
}
}
export const getStaticProps = async ({ params }) => {
const { items } = await client.getEntries({
content_type: 'myPosts',
'fields.slug': params.slug,
})
if (!items.length) {
return {
notFound: true,
}
}
return {
props: { item: items[0] },
revalidate: 10,
}
}
const PostDetailsPage = ({ item }) => {
console.log(item.fields.content)
return (
<Layout title={item.fields.slug}>
<div className="w-full">
<div className="max-w-6xl flex flex-wrap justify-between items-center mx-auto py-6 px-5 gap-3">
<div>
<h1 className="font-bold text-3xl sm:text-4xl md:text-4xl lg:text-5xl py-3 mb-1">
{item.fields.title}
</h1>
<p className="font-bold text-2xl mb-2">
{item.sys.createdAt.substring(0, 10)}
</p>
</div>
<div>
<Image
src={`https:${item.fields.image.fields.file.url}`}
alt={item.fields.image.fields.description}
width={500}
height={500}
className="rounded"
/>
</div>
</div>
</div>
<div className="max-w-6xl mx-auto p-5">
{documentToReactComponents(item.fields.content, options)}
</div>
</Layout>
)
}
export default PostDetailsPage
Discussion