1
3

More than 3 years have passed since last update.

Next.js+TailwindCSS製 個人Blog構築の流れ -後編-

Last updated at Posted at 2021-03-08

※本記事は、私が作成した こちらのブログ ⑤〜⑧ の内容をまとめて上げたものです。
前回投稿した ①〜④ はこちら
語弊を恐れずなるべく平易に書こうとしておりますので、細かなニュアンスの違い等はご容赦頂ければと思います。誤植や記述内容の間違い等ございましたら遠慮なくコメント頂ければ幸いです。

|ローダーのセットアップ

まずは、3つのライブラリをインストールしていきます。

ターミナルを開き、アプリのディレクトリに移動し、以下のコマンドを入力してください。

npm install react-markdown gray-matter raw-loader

続いて、row-loader でマークダウンファイルを読み込むための Next.js の設定ファイルを作成していきます。
package.json がある階層と同じ場所にnext.config.jsファイルを作成してください。
next.config.jsですよ!nextconfig.js ではないですからね... このドット(.)が無いだけで私は2時間も時間を溶かしてしまいました(T.T)

ファイル作成ができたら、以下のように記述してください。

// next.config.js
module.exports = {
  target: 'serverless',
  webpack: function (config) {
    config.module.rules.push({
      test: /\.md$/,
      use: 'raw-loader',
    })
    return config
  },
}

次に、読み込み用のマークダウンファイルを作成していきます。

public などと同じトップの階層にpostsディレクトリを作成し、その中にmypost.mdファイルを作成し、以下のように記述してください。
改行用に各行の末尾に半角スペース2個含まれたりしていますのでコピペしてしまってください。

---
title: 'ブログのタイトル'
author: 'ブログの筆者'
---
↓ここからマークダウンのボディ↓  
色んなマークダウンの書き方を試してみて、  
`どのように表示されるか`確認してみましょう!
- リスト1
- リスト2
- リスト3

最後にこのマークダウンファイルを読み取る部分の作成をしていきましょう!
[post].jsを編集しましょう!少し長いですが以下のように書き換えてください。

// [post].js
import matter from 'gray-matter'
import ReactMarkdown from 'react-markdown'

export default function BlogPost({ frontmatter, markdownBody }) {
  if (!frontmatter) return <></>

  return (
    <article>
      <h1>{frontmatter.title}</h1>
      <p>By {frontmatter.author}</p>
      <div>
        <ReactMarkdown source={markdownBody} />
      </div>
    </article>
  )
}

export async function getStaticProps({ ...ctx }) {
  const { post } = ctx.params

  const content = await import(`../../posts/${post}.md`)
  const data = matter(content.default)

  return {
    props: {
      frontmatter: data.data,
      markdownBody: data.content,
    },
  }
}

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('../../posts', true, /\.md$/))

  const paths = blogSlugs.map((slug) => `/post/${slug}`)

  return {
    paths,
    fallback: false,
  }
}

各種ファイルを作成したあと、開発サーバーを立ち上げている場合は再起動してください。
立ち上げていない場合はターミナルにてnpm run devをしてください。
http://localhost:3000/post/mypostにアクセスしてみると以下のような画面がみれましたでしょうか?
mypost.png
いくつかに分けて解説をしていきます!

● getStaticProps()

まずは、BlogPostファンクションの下にあるgetStaticProps()からですが、前回でもこのファンクションの説明はしてありますが、もう一度コンパクトにお伝えするとデータをビルド時にページコンポーネントに引き渡すファンクションです。
今回のものでいうとマークダウンファイルのデータを取得・解析し、[post].js のページに引き渡す役割を担っているという感じでしょうか。

今回返却しているデータはfrontmattermarkdownBodyの2つです。
frontmatter には何が入っているのかというと、mypost.md ファイルの冒頭で記述した

---
title: 'ブログのタイトル'
author: 'ブログの筆者'
---

の部分(FrontMatter)をgray-matterというライブラリを用いて解析し、以下のようなJSONデータとして返却しています。

{title: 'ブログのタイトル', author: 'ブログの筆者'}

また、markdownBody の中には、マークダウンファイルで記述した FromtMatter より下のテキストがcontentというキーでJSONデータの中にそのまま格納されています。
これについては後述しますが、変換作業が必要です。

● ReactMarkdown

BlogPost ファンクションの中に<ReactMarkdown source={markdownBody} />という記述があるかと思います。
こちらはreact-markdownというライブラリを使って、マークダウンで記述されているテキストをHTMLに変換している部分です。
source={markdownBody}という形で引き渡してあげても構いませんし、<ReactMarkdown># Hello, *world*!</ReactMarkdown>という形で挟んであげても大丈夫です!
次回以降の記事でスタイルを整えていきますが、マークダウンで書かれたテキストが、どのようなHTMLに変換されているかについては色々と試して確認してみてくださいね @@/

● getStaticPaths()

最後に getStaticPaths() について説明して、この記事を終えたいと思います。
getStaticPaths()とは、getStaticProps() と同様に Next.js が用意しているファンクションです。どのようなことができるのかというのをザックリ言ってしまうと、「ビルド時に特定のデータに基づいて動的ルートを静的に生成する」という感じかなと思います。@@;
ブログでいうと記事が複数ある場合、その記事の全てのルートは確保しておきたいですよね。
その記事のルートを確保するために、今回でいうと、マークダウンファイルの名称をルートとして作成しよう!としているわけです。
つまるところ、pages ディレクトリに post01.mdファイルとpost02.mdファイルが格納されている場合、/post/post01post/post02のアクセスに関しては記事の表示をするが、それ以外は404ページ(return 内の fallback: false が該当箇所です!)を表示するというコントロールをおこなっているという感じです!

いつものごとく、詳しくはNext公式getStaticPaths
をご確認ください。
機能面はこれで概ね完成ですね!超絶に簡易ですが、ブログシステムができました〜 ^^v

|表示コンテンツの整理

ブログシステムの構築も終盤となってまいりました!もうしばらくお付き合いください。
それではやっていきましょう!まずは、トップページに表示するコンテンツを考えていきます。
トップページでは、ブログ記事の一覧と、記事へのリンクを設置していきたいと思います!
components/PostList.jsを開き、以下のように記述してください。

// PostList.js
import Link from 'next/link'

export default function PostList({ posts }) {
  if (posts === 'undefined') return null

  return (
    <div>
      {!posts && <div>No posts!</div>}
      <ul>
        {posts &&
          posts.map((post) => {
            return (
              <div key={post.slug} className="container mx-auto">
                <Link href={{ pathname: `/post/${post.slug}` }}>
                  <div className="text-2xl mt-20 hover:underline hover:text-blue-800">
                    {post.frontmatter.title}
                  </div>
                </Link>
                <div className="flex items-center">
                  {post.frontmatter.author}
                </div>
                <div className="mt-8 mb-10 text-justify">
                  {post.frontmatter.excerpt}
                </div>
                <Link href={{ pathname: `/post/${post.slug}` }}>
                  <a className="underline hover:text-blue-800">続きを読む </a>
                </Link>
              </div>
            )
          })
        }
      </ul>
    </div>
  )
}

それから、pages/index.jsを以下のように修正してください。

// index.js
import matter from 'gray-matter'
import Layout from '../components/Layout'
import PostList from '../components/PostList'

const Index = ({ title, description, posts }) => {

  return (
    <Layout pageTitle={title}>
      <div>ここがLayoutコンポーネントのChildren部分です</div>
      <div>{description}</div>
      <PostList posts={posts} />
    </Layout>
  )
}

export default Index

export async function getStaticProps() {
  const configData = await import(`../siteconfig.json`)

  const posts = ((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,
        markdownBody: document.content,
        slug,
      }
    })

    return data
  })(require.context('../posts', true, /\.md$/))

  return {
    props: {
      posts,
      title: configData.default.title,
      description: configData.default.description,
    },
  }
}

また、記事の概要についてもリスト内で表示させたいのでmypost.mdファイルの FrontMatter 部分にexcerpt項目を追加してください。

// mypost.md (※この行は含めないでください!)
---
title: 'ブログのタイトル'
author: 'ブログの筆者'
excerpt: 'ブログ記事の概要をここに記述ブログ記事の概要をここに記述ブログ記事の概要をここに記述ブログ記事の概要をここに記述'
---
↓ここからマークダウンのボディ↓  
色んなマークダウンの書き方を試してみて、  
`どのように表示されるか`確認してみましょう!
- リスト1
- リスト2
- リスト3

この状態で画面を確認してみると以下のようになっているかと思います。
少し不恰好ですが、後でレイアウト修正しますので、もうしばらく我慢してください。

post_list.png

現在、記事の件数(マークダウンファイルの枚数)は1件しかありません。この状態では複数ある場合の画面を確認できないので、postsディレクトリに別のマークダウンファイルを作成してみましょう!
another_post.mdファイルを作成し、中身を記述してください。

---
title: 'ブログのタイトル②'
author: 'ブログの筆者②'
excerpt: 'ブログ記事の概要をここに記述②ブログ記事の概要をここに記述②ブログ記事の概要をここに記述②ブログ記事の概要をここに記述②'
---
ブログ記事の中身です。ブログ記事の中身です。ブログ記事の中身です。ブログ記事の中身です。ブログ記事の中身です。ブログ記事の中身です。

保存後画面の確認をしてください!無事2つ目の記事がリストアップされましたでしょうか^^?
ちなみに、Footerコンポーネントが意図した部分にないですよね ^^;
Lyaout.jsで{children}の div にh-20が当たっているのが原因なのですが、レイアウトの修正自体は後でおこなっていくので、心配しないでください!
気になってムズムズが止まらない方は、<div className="bg-yellow-300 h-20">{children}</div>h-20を削除しておいてください!

次に、ブログ記事詳細ページも修正をしていきましょう。[post].jsファイルを開き、下記のようにしてください。

// [post].js
import Link from 'next/link'
import matter from 'gray-matter'
import ReactMarkdown from 'react-markdown'
import Layout from '../../components/Layout'

export default function BlogPost({ siteTitle, frontmatter, markdownBody }) {
  if (!frontmatter) return <></>

  return (
    <Layout pageTitle={`${siteTitle} | ${frontmatter.title}`}>
        <Link href="/">
          <a className="underline"> トップページに戻る</a>
        </Link>
        <article>
          <h1>{frontmatter.title}</h1>
          <p>By {frontmatter.author}</p>
          <div>
            <ReactMarkdown source={markdownBody} />
          </div>
        </article>
    </Layout>
  )
}

export async function getStaticProps({ ...ctx }) {
  const { post } = ctx.params

  const config = await import(`../../siteconfig.json`)
  const content = await import(`../../posts/${post}.md`)
  const data = matter(content.default)

  return {
    props: {
      siteTitle: config.title,
      frontmatter: data.data,
      markdownBody: data.content,
    },
  }
}

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('../../posts', true, /\.md$/))

  const paths = blogSlugs.map((slug) => `/post/${slug}`)

  return {
    paths,
    fallback: false,
  }
}

大きな変更点は、レイアウトコンポーネントの反映と、トップページに戻るためのリンクの設置です。この時点で、URLに値を入力することなく、画面上で全ページへのルーティングが通ったかと思います。一度存分にルーティングを体感してみてください〜

●デザイン修正

表示コンテンツの整理(文言等は未修正だが...)はできましたので、次に、デザインの修正に取り掛かっていきましょう!
まずは、Header から修正します。Header.jsを開き、以下のように修正してください。

// Header.js
import Link from 'next/link'
export default function Header() {
  return (
    <header className="bg-black text-white sticky top-0">
      <nav className="mb-20 flex items-center h-20">
        <Link href="/">
          <a className="pl-8 md:pl-20 lg:pl-40 xl:pl-64 2xl:pl-80">My Blog</a>
        </Link>
        <Link href="/about">
          <a className="pl-20">About</a>
        </Link>
      </nav>
    </header>
  )
}

続いて、Footer の修正をしましょう!Footer.jsを開き、以下のように修正してください。

// Footer.js
import Link from 'next/link'
export default function Footer() {
  return (
    <footer className="text-white text-xs">
      <div className="bg-gray-900 flex-col text-center cursor-pointer">
        <Link href="/">
          <div className="h-20 flex justify-center items-center">
            Top Page
          </div>
        </Link>
        <Link href="/about">
          <div className="h-20 flex justify-center items-center border-t border-gray-500">
            About Page
          </div>
        </Link>
      </div>
      <div className="bg-black h-10 flex justify-center items-center">
        &copy;Daisuke All Rights Reserved.
      </div>
    </footer>
  )
}

続いてLayout.jsの修正を行います!以下のように変更してください。

// Layout.js
import Head from 'next/head'
import Header from '../components/Header'
import Footer from '../components/Footer'

export default function Layout({ children, pageTitle }) {
  return (
    <>
      <Head>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>{pageTitle}</title>
      </Head>
      <section>
        <Header />
        <div className="m-8 md:mx-14 lg:mx-40 xl:mx-64 2xl:mx-80 mb-20">
          {children}
        </div>
      </section>
      <Footer />
    </>
  )
}

非常にシンプルなデザインとなっておりますが、Header と Footer、Layout の修正が終わり、
デザイン面は整ってきたのではないでしょうか ^^/

after_design.png

最後に、記事詳細のデザインを少しだけ整えましょう!
[post].jsの BlogPostファンクションを以下のように修正してください。

// [post].js の BlogPost ファンクションを修正
export default function BlogPost({ siteTitle, frontmatter, markdownBody }) {
  if (!frontmatter) return <></>

  return (
    <Layout pageTitle={`${siteTitle} | ${frontmatter.title}`}>
      <Link href="/">
        <a className="underline"> トップページに戻る</a>
      </Link>
      <article className="mt-10">
        <h1 className="text-2xl mb-4">{frontmatter.title}</h1>
        <p className="mb-6">By {frontmatter.author}</p>
        <div>
          <ReactMarkdown source={markdownBody} />
        </div>
      </article>
    </Layout>
  )
}

post_page.png
お気づきかと思いますが、マークダウンで書かれた部分... デザインが無いですよね^^;

|記事へのCSS反映

それでは、記事の部分へのCSSを反映させていきましょう!

まずは[post].jsファイルの修正からやっていきます。
以下のように<ReactMarkdown>を囲っている div タグにmarkdownクラスを付与してください。

// [post].js の BlogPostファンクション return 内
<div className="markdown">
  <ReactMarkdown source={markdownBody} />
</div>

続いて、globals.cssファイルを以下のように修正してください。

/* globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

/* Markdown Styles */
/* Global */
.markdown {
    @apply leading-relaxed text-sm;
}
@screen sm {
  .markdown {
    @apply text-base;
  }
}
@screen lg {
  .markdown {
    @apply text-lg;
  }
}

/* Headers */
.markdown h1,
.markdown h2 {
  @apply text-xl my-6;
}
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
  @apply text-lg my-3 ;
}
@screen sm {
  .markdown h1,
  .markdown h2 {
    @apply text-2xl;
  }
  .markdown h3,
  .markdown h4,
  .markdown h5,
  .markdown h6 {
    @apply text-xl;
  }
}

/* Links */
.markdown a {
  @apply text-blue-600;
}
.markdown a:hover {
  @apply underline;
}

/* Paragraph */
.markdown p {
  @apply mb-4 leading-8 md:leading-10 text-justify;
}

/* Lists */
.markdown ul,
.markdown ol {
  @apply mb-4 ml-8;
}
.markdown li > p,
.markdown li > ul,
.markdown li > ol {
  @apply mb-0;
}
.markdown ol {
  @apply list-decimal;
}
.markdown ul {
  @apply list-disc;
}

/* Blockquotes */
.markdown blockquote {
  @apply p-2 mx-2 my-2 bg-gray-100 mb-4 border-l-4 border-gray-400  rounded-r-lg;
}
.markdown blockquote > p {
  @apply mb-0;
}

/* Images */
.markdown img {
  @apply shadow-lg;
}

/* Code */
.markdown :not(pre) > code {
  @apply bg-indigo-50 p-1 font-semibold text-gray-600 rounded-lg ;
}

/* Pre */
.markdown pre {
  @apply mx-2;
}

全てのマークダウン記法に対応できている訳では無く、最低限これくらいあればいいかな〜という範囲を記述しています!
もし、ご自身で不足しているものがある場合は、適宜追加してください。(コードブロックのシンタックスハイライトはこの後対応します。)
では、一度画面を確認してみましょう!色々なパターンのマークダウン記法で書いて確認してみてください!
markdown_design.png
反映されてそうですね ^^b

では、最後にコードブロックのシンタックスハイライトを対応していきましょう!
今回は、react-syntax-highlighterというライブラリを使いたいと思います。

まずは、ライブラリのインストールからしていきましょう!
ターミナルでアプリのトップの階層に移動して以下のコマンドを入力してください。

npm install react-syntax-highlighter --save

インストールが成功したら、次にコードブロック用のコンポーネントを作っていきます!
components ディレクトリにCodeBlock.jsファイルを作成し、以下のように記述してください。

// CodeBlock.js
import React from "react"
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import { base16AteliersulphurpoolLight } from "react-syntax-highlighter/dist/cjs/styles/prism"

const CodeBlock = ({ language, value }) => {
  return (
    <SyntaxHighlighter language={language} style={base16AteliersulphurpoolLight}>
      {value}
    </SyntaxHighlighter>
  )
}
export default CodeBlock

このコンポーネントを<ReactMarkdown>内で読み込んであげると、コードブロックにシンタックスハイライトが反映されます。
ということで、[post].jsを以下のように修正してください!

import Link from 'next/link'
import matter from 'gray-matter'
import ReactMarkdown from 'react-markdown'
import Layout from '../../components/Layout'
import CodeBlock from "../../components/CodeBlock" //←追記

export default function BlogPost({ siteTitle, frontmatter, markdownBody }) {
  if (!frontmatter) return <></>

  return (
    <Layout pageTitle={`${siteTitle} | ${frontmatter.title}`}>
        <Link href="/">
          <a className="underline"> トップページに戻る</a>
        </Link>
        <article className="mt-10">
          <h1 className="text-2xl mb-4">{frontmatter.title}</h1>
          <p className="mb-6">By {frontmatter.author}</p>
          <div className="markdown">
            <ReactMarkdown
              source={markdownBody}
              renderers={{ code: CodeBlock }} //←追記
            />
          </div>
        </article>
    </Layout>
  )
}
// 以下 getStaticProps(), getStaticPaths() が続きます

この状態で、マークダウンファイルにコードブロックを記述してみてください!
PHPや JavaScript といった言語を指定したい場合は、 ``` javascript という感じでコードブロックの開始バッククオートの横に書いてあげると読み込むことができます。反映されているか画面を確認してみましょう!

code_block.png
無事反映していることが確認できました〜 ^^v
ということで、以上で完成です!!
細かな部分のデザインの修正やアレンジの続きは読者の皆様に委ねます!

|Netlify へのデプロイ

とうとうこのシリーズも最終回となりました。
せっかくMyブログを作ったのだから、公開するまでしないと!ですよね ^^
公開する場所はNetlifyというホスティングサービスを利用したいと思います!
一緒にデプロイしようという方は、Netlifyのアカウントの作成をしておいてください。
アカウント自体は後ほどGitHub等のソース管理サービスのアカウントと連携をしますので、そちらで登録をしてください。

  • Netlify - 簡単なことであれば無料で利用することが可能です!

アカウントの登録ができたら、デプロイの準備をしましょう!
まず、next.config.jsと同階層にnetlify.tomlというファイルを作成し、以下のように記述してください。

[build]
  command = "npm run build && npm run export"
  publish = "out"

先ほど記述したコマンドをpackage.jsonファイルに追記します!以下のように修正してください。

// package.json  scripts部分
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",   //←カンマ忘れずに
    "export": "next export"  //←追記
  },

ここまでできたら、ソースコードをGitHubにプッシュしましょう!
作成した GitHub のリポジトリと紐付けて、マークダウン記事のPUSH時に自動でビルドが走るようにします!
プッシュができましたら、Netlifyの管理画面に進みNew site from Gitというボタンを押してください。
create_new.png
GitHubを選択し、先ほどソースコードをプッシュしたリポジトリを選択します。
そうすると、最終的にビルド時の設定ができる画面に移動しますので、Build command の部分をnpm run build && npm run exportに変更、Publish directory の部分をoutに変更してDeploy siteボタンを押してください!
netlify_setting.png
すると次の画面に遷移し、ビルドが走ります!
build.png
ビルド時のログも見ることができます!ログを確認し、最終的に以下のように書かれていれば完了です!
finish.png
ページのトップに戻って Preview Deploy でデプロイできているか確認しましょう!
preview.png
ブログのトップページが確認できましたら完了となります ^^v

お疲れ様でした!

まとめ

Next.js & TailwindCSS を使ってのブログ構築の解説は以上となります!
いかがでしたでしょうか^^ ?
細かく解説したい部分もいくつかあったのですが、あまり冗長になってしまうと、かえってわかりにくくなることも懸念されましたので、飛ばすところはサラっと飛ばしてあります。
記述内容に間違いがあったり、分かりにくいところなどございましたら、Twitter の方に連絡いただければと思います!

ご拝読ありがとうございました〜
それではまたお会いしましょう!

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3