※本記事は、私が作成した こちらのブログ ⑤〜⑧ の内容をまとめて上げたものです。
前回投稿した ①〜④ はこちら
語弊を恐れずなるべく平易に書こうとしておりますので、細かなニュアンスの違い等はご容赦頂ければと思います。誤植や記述内容の間違い等ございましたら遠慮なくコメント頂ければ幸いです。
|ローダーのセットアップ
まずは、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
にアクセスしてみると以下のような画面がみれましたでしょうか?
いくつかに分けて解説をしていきます!
● getStaticProps()
まずは、BlogPostファンクションの下にあるgetStaticProps()
からですが、前回でもこのファンクションの説明はしてありますが、もう一度コンパクトにお伝えするとデータをビルド時にページコンポーネントに引き渡すファンクション
です。
今回のものでいうとマークダウンファイルのデータを取得・解析し、[post].js のページに引き渡す役割を担っているという感じでしょうか。
今回返却しているデータはfrontmatter
とmarkdownBody
の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/post01
とpost/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
この状態で画面を確認してみると以下のようになっているかと思います。
少し不恰好ですが、後でレイアウト修正しますので、もうしばらく我慢してください。
現在、記事の件数(マークダウンファイルの枚数)は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">
©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 の修正が終わり、
デザイン面は整ってきたのではないでしょうか ^^/
最後に、記事詳細のデザインを少しだけ整えましょう!
[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>
)
}
お気づきかと思いますが、マークダウンで書かれた部分... デザインが無いですよね^^;
|記事への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;
}
全てのマークダウン記法に対応できている訳では無く、最低限これくらいあればいいかな〜という範囲を記述しています!
もし、ご自身で不足しているものがある場合は、適宜追加してください。(コードブロックのシンタックスハイライトはこの後対応します。)
では、一度画面を確認してみましょう!色々なパターンのマークダウン記法で書いて確認してみてください!
反映されてそうですね ^^b
では、最後にコードブロックのシンタックスハイライトを対応していきましょう!
今回は、react-syntax-highlighter
というライブラリを使いたいと思います。
- react-syntax-highlighter - npm -
まずは、ライブラリのインストールからしていきましょう!
ターミナルでアプリのトップの階層に移動して以下のコマンドを入力してください。
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 という感じでコードブロックの開始バッククオートの横に書いてあげると読み込むことができます。反映されているか画面を確認してみましょう!
無事反映していることが確認できました〜 ^^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
というボタンを押してください。
GitHub
を選択し、先ほどソースコードをプッシュしたリポジトリを選択します。
そうすると、最終的にビルド時の設定ができる画面に移動しますので、Build command の部分をnpm run build && npm run export
に変更、Publish directory の部分をout
に変更してDeploy site
ボタンを押してください!
すると次の画面に遷移し、ビルドが走ります!
ビルド時のログも見ることができます!ログを確認し、最終的に以下のように書かれていれば完了です!
ページのトップに戻って Preview Deploy でデプロイできているか確認しましょう!
ブログのトップページが確認できましたら完了となります ^^v
お疲れ様でした!
まとめ
Next.js & TailwindCSS を使ってのブログ構築の解説は以上となります!
いかがでしたでしょうか^^ ?
細かく解説したい部分もいくつかあったのですが、あまり冗長になってしまうと、かえってわかりにくくなることも懸念されましたので、飛ばすところはサラっと飛ばしてあります。
記述内容に間違いがあったり、分かりにくいところなどございましたら、Twitter の方に連絡いただければと思います!
ご拝読ありがとうございました〜
それではまたお会いしましょう!