- Published on
ブログをNext.jsで作り直した
- Authors
- Name
- nu0ma
- @nu0ma
元々このブログは Gatsby で作っていたが、TypeScript に対応していなかったり、見た目が気に入らなかったりで、 最近人気のある Next.js でついでに作り直そうとなったので Next.js に移行した。 記事自体はマークダウンで書いているのでそこは移動するだけでいい。 Vercel と GitHub の連携で push されると自動で vercel に再デプロイされる。
使用したライブラリ
スタイルはTailwind CSSを使用し、コードハイライトはPrismを使用している。markdown から html への変換はremarkを使用している。 remark を選んだ理由は、マークダウンパサーのnpm trendを見て決めた(remark の README に書いてある)。 markdown ファイルには front-matter で title や slug や date を書いているので、一度matterでそれらを書き出してから、remark に渡すという流れになっている。
matter に下記の front-matter を与えると
---
title: Hello
slug: home
---
<h1>Hello world!</h1>
{
content: '<h1>Hello world!</h1>',
data: {
title: 'Hello',
slug: 'home'
}
}
content
とdata
に分割され、data
にfront-matter
で設定した項目が入る(gray-matter#what-does-this-do より)
全ての記事の取得
上記で説明した[slug].tsx
での各記事の取得は下記コード。index.tsx
にあたる。 content/posts/post-01.md
のような形で posts フォルダに markdown で書いた記事があるので、それを呼び出す。 ブログなので、リクエストによって内容が変わることもないため、ビルド時にデータがフェッチされて入っていれば良く、getStaticProps
でデータを取得する。
export default function Home({ posts }){
.
.
.
}
export const getStaticProps: GetStaticProps = async () => {
const files = fs.readdirSync(`${process.cwd()}/content/posts`)
const posts = files
.map((filename) => {
const markdownWithMetadata = fs
.readFileSync(`content/posts/${filename}`)
.toString()
const { data } = matter(markdownWithMetadata)
const options = { year: 'numeric', month: 'long', day: 'numeric' }
const formattedDate = data.date.toLocaleDateString('ja-JP', options)
const frontmatter = {
...data,
date: formattedDate,
}
return {
slug: filename.replace('.md', ''),
frontmatter,
}
})
.reverse()
return {
props: {
posts,
},
}
}
あとはコンポーネントの方で getStaticProps から取得した記事データを整形して表示させればいい。
個別の記事ページ
個別の記事([slug].tsx
)でも大体同じ流れ。 ルートのparams
からslug
を取ってきて content/posts/[slug].md
にある記事をgetStaticProps
で表示する。
export default function Home({
posts,
}: InferGetStaticPropsType<typeof getStaticProps>): JSX.Element {
.
.
.
}
export const getStaticProps: GetStaticProps = async (context) => {
const { slug } = context.params
const markdownWithMetadata = fs.readFileSync(
path.join('content/posts', slug + '.md'),
)
const { data, content: body } = matter(markdownWithMetadata)
const content = await markdownToHtml(body)
const options = { year: 'numeric', month: 'long', day: 'numeric' }
const formattedDate = data.date.toLocaleDateString('ja-JP', options)
const metaData = {
date: formattedDate,
title: data.title,
}
return {
props: {
metaData,
content,
},
}
}
export async function getStaticPaths() {
const files = fs.readdirSync('content/posts')
const paths = files.map((filename) => ({
params: {
slug: filename.replace('.md', ''),
},
}))
return {
paths,
fallback: false,
}
}
ここまでで md の記事が見えるようにはなった。 次に CSS フレームワークとして Tailwind CSS をいれる。
Tailwind CSS の導入
Next.js に Tailwind CSS を入れる方法は公式のInstall Tailwind CSS with Next.jsの通りにすればいい。導入が終わると、markdown のスタイルが崩れる。これは Tailwind のスタイルがあたっているので、新たに markdown のためにスタイルを作って import する必要がある。
blog-starter-typescriptにも載っている通り、markdown-styles.module.css
を新たに作って、h1
などに CSS を設定すればいい。
blog-starter-typescriptを基本的に参考にして、list や blockquote は個別に設定した。
.markdown {
@apply text-base leading-relaxed;
}
.markdown p,
.markdown ol,
.markdown ul,
.markdown blockquote {
@apply my-6;
}
.markdown h1 {
@apply mt-14 mb-4 text-3xl font-semibold leading-snug;
}
.markdown h2 {
@apply mt-12 mb-4 text-2xl font-semibold leading-snug;
}
.markdown h3 {
@apply mt-8 mb-4 text-xl font-medium leading-snug;
}
.markdown ol {
@apply ml-6 list-decimal;
}
.markdown ul {
@apply ml-6 list-disc;
}
.markdown a {
@apply text-blue-500 underline;
}
.markdown blockquote {
@apply mx-2 mb-4 border-l-4 border-gray-400 bg-gray-100 p-2 italic;
}
Styling markdown posts with Tailwind CSS in GatsbyJSは Gatsby の例だがこちらにはさらに詳細に設定された CSS ファイルが載っている。
また、Style a Blockquote using Tailwind CSSに引用のスタイルの例が載っている。 これらを参考にした。
作成した CSS ファイルと[slug].tsx
で読みこみ、スタイルをあてる。
// [slug].tsx
import markdownStyles from '../../styles/markdown-styles.module.css'
export default function Post({
metaData,
content,
}: InferGetStaticPropsType<typeof getStaticProps>): JSX.Element {
useEffect(() => {
Prism.highlightAll()
}, [])
const router = useRouter()
return (
<Layout>
{router.isFallback ? (
<div>Loading...</div>
) : (
<div>
<div className="text-3xl mg:text-4xl font-semibold text-gray-700">
{metaData.title}
</div>
<div
className={markdownStyles['markdown']}
dangerouslySetInnerHTML={{ __html: content }}
></div>
</div>
)}
</Layout>
)
}
...
Prism の導入
Using Prism.js in a Next.js siteを参考に行った。 CSS をダウンロードして_app.tsx
で import し、Prismjs
をnpm or yarn
でインストールして、
useEffect(() => {
Prism.highlightAll()
}, [])
を Layout コンポーネントに書けば良い。
まとめ
ダークモードと 404 ページを追加したい。