Published on

ブログをNext.jsで作り直した

Authors

元々このブログは Gatsby で作っていたが、TypeScript に対応していなかったり、見た目が気に入らなかったりで、 最近人気のある Next.js でついでに作り直そうとなったので Next.js に移行した。 記事自体はマークダウンで書いているのでそこは移動するだけでいい。 Vercel と GitHub の連携で push されると自動で vercel に再デプロイされる。

lighthouseの画像

使用したライブラリ

スタイルは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'
  }
}

contentdataに分割され、datafront-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 し、Prismjsnpm or yarnでインストールして、

useEffect(() => {
  Prism.highlightAll()
}, [])

を Layout コンポーネントに書けば良い。

まとめ

ダークモードと 404 ページを追加したい。