5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-03-05

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

|開発動機

Reactの基礎的な学習を終え、何か1つサンプル的なアプリを作りたいなと考えておりまして...
ふと、「情報発信するための自身のBlog」を持っていないなと思い、せっかくなら WordPress ではなく、自作の Blog を作ってみようと思い立ち開発に至りました。
ただ、サクッと作ってしまいたいという気持ちもあったので、ReactのフレームワークであるNext.jsを採用することにしました!
また、CSSが超絶苦手な私はTailwindCSSさんのお力を借りる事にいたしました^^;

【技術スタック】

 ・ Next.js v10.0.7
 ・ React v17.0.1
 ・ TailwindCSS v2.0.3

記事につきましてはMarkdownファイルで作成したものを読み込み、レンダリングするようにしました。
読み込み・変換 用のライブラリとして以下のものを活用しました。

 ・ raw-loader v4.0.2
 ・ gray-matter v4.0.2
 ・ react-markdown v5.0.3
 ・ react-syntax-highlighter v15.4.3

raw-loaderはファイルを文字列としてインポートできるwebpackのローダーです。
gray-matter は文字列またはファイルからFrontMatterを読み取り解析するものです。今回はMarkdownファイルの冒頭に以下のように書き込んでおくとJSONデータとして吐き出せるようにしました。

---
title: 'ここにブログのタイトルを書きます'
author: 'ここには作者名を書きます'
date: '20XX年XX月XX日'
excerpt: 'ここに記事の概要を書き込んでおくとトップページで表示できます'
---

react-markdownはローダーで読み取ったMarkdownファイルのテキストデータをHTMLに変換するためのライブラリです。
react-syntax-highlighterはコードブロックのシンタックスハイライトを付けてくれるライブラリです。

以上がこのブログで使っているライブラリです。

【デプロイどうしようか】

今回構築したブログはDBを持たず、/postsディレクトリにMarkdownファイルを設置してビルドするだけで完了という構成にしています。
つまり、静的なサイトを生成している(SSG)という感じです。
サーバー自体はレンタルサーバーなどでもよかったのですが、せっかくなら色んな事にチャレンジしたいと思い、Netlifyのようなホスティングサービスを利用し、加え、GitHubとの連携をしてPUSH時に自動ビルドが走るようにしてみようと考えました!なんちゃってCI/CD環境の構築を目指してみたわけです^^;

|環境構築

それでは、早速Next.jsのインストールから行なっていきたいと思います。
とその前に、Next.js アプリを作成するためには Node.jsをインストールしておく必要があります。公式ドキュメントによりますと、Node.js 10.13 以上のバージョンがインストールされていることが条件となります。この記事を進められる前に、Node.js のインストール作業をお願い致します。

アプリ作成

では、早速アプリの作成をしていきましょう!
ターミナルを開いて、アプリを作成したいディレクトリに移動してください。移動先で以下のコマンドを入力してください。

 npx create-next-app [project-name]

[project-name]の部分はご自身でお好きな名前を付けてください![]は不要です。
以下のような文言が出ていれば完了です!

Success! Created sample at /保存先/[project-name]
Inside that directory, you can run several commands:

  yarn dev
    Starts the development server.

  yarn build
    Builds the app for production.

  yarn start
    Runs the built app in production mode.

We suggest that you begin by typing:

  cd [project-name]
  yarn dev

一度Next.jsのTOP画面を拝んでおきましょう!

 cd [project-name]  // アプリのディレクトリに移動して
 npm run dev        // または yarn dev でもいいです

すると、コンパイルが始まって、しばらく経つとcompiled successfullyという文字が表示されると思いますので、ブラウザから http://localhost:3000 にアクセスすると以下のようなTOPページが確認できるかと思います!
top_page.png
やりましたね! ^^v
次にTailwindCSSのセットアップに移りましょう!
ターミナルにて以下のコマンドを入力
(※以下のコマンドはプロジェクトディレクトリ内で入力してください)

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest

次にコンフィグファイルを作成していきます。
以下のコマンドを入力するとtailwind.config.jspostcss.config.jsが自動的に生成されます。

npx tailwindcss init -p

生成されたtailwind.config.jsファイルを編集していきましょう。生成時はpurgeの値が空っぽになっているかと思いますので、以下のように追記します。

tailwind.config.js
module.exports = {
  purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

続いてCSSファイルにTailwindCSSの設定を読み込むように変更します。
/stylesディレクトリ内にあるglobals.cssを開き、中身を全て消去し、以下の3行に書き換えてください。

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

以上で TailwindCSS の設定は完了です! ちなみに、このCSSファイルはpages/_app.jsの中でインポートしています。
もし、他のCSSファイルを作成した時は、適宜読み込みを行なってください!
ということで、一応確認のためと Next.js の動きの確認のためにHello World!!を表示させてみましょう!
pages/index.jsファイルを開いてください。以下のようになっているかと思います。

index.js
import Head from 'next/head'
import styles from '../styles/Home.module.css'

export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        ...
        中略
        ...
      </footer>
    </div>
  )
}

このファイルのインポート部分の削除とHome()ファンクションの return の中の<Head>タグから</footer>を全て消去し、以下のように書き換えてください。

index.js
export default function Home() {
  return (
    <div className="text-red-400">
      Hello World!
    </div>
  )
}

変更後、ターミナルでnpm run devを実行してください。※コマンドを実行する場所はアプリのあるディレクトリ内でです。
実行後にhttp://localhost:3000にアクセスすると...
hello_world.png
赤字で Hello World!! が表示されましたね ^^v
React で class を付与させるには class="hoge" と書くのではなく、className="hoge"と書く必要があります。
今回の divタグ内のtext-red-400は TailwindCSS のクラスで、文字色を赤の400にするというものです。色見本については TailwindCSS公式ドキュメント で確認してみてください!また、他のどのようなクラスがあるのかも見てみてください〜

続いて、簡単にルーティングについても確認しておきたいと思います。
npm run dev は止めずに、そのままで進めていきましょう!止めた方は、再度実行しておいてください!
それでは、まずpagesディレクトリ内にabout.jsファイルを作成してください。そして、以下のように書き加えてください。

about.js
const About = () => {
  return (
    <div className="text-center mt-10">
      about
    </div>
  )
}
export default About

変更を保存後、http://localhost:3000/aboutにアクセスすると about という文字列が表示されているかと思います。
Next.js は /pages ディレクトリに js のエクスポートファイルを設置すると、ビルド時にファイル名をURLとしてルーティングに組み込んでくれます^^(便利!)

|Component分割とレイアウトの作成

コンポーネントの分割の思想については、様々な考え方がございますので、この記事内では深く言及することは避けます。
かなりフランクに捉えて頂いて、分割するにはどうすればいいかという部分のみ掴んでいただければ幸いです。

では、早速コーディングをしていきましょう!と、その前に今回使うことのない、不要なファイル達を削除しておきたいと思います。
削除後に残るディレクトリやファイルは以下のようになりますので、それ以外のファイルは削除してしまいましょう!
一応具体的に削除するファイルやディレクトリを挙げておくと、public/vercel.svgstyles/Home.module.cssapiディレクトリは中のhello.jsごと削除してしまってください。

.next/
node_modules/
pages/
  _app.js
  about.js
  index.js
public/
  favicon.ico
styles/
  global.css
.gitignore
package-lock.json
package.json
postcss.config.js
README.md
tailwind.config.js
yarn.lock

それでは、ここからファイルやディレクトリを作成していきます!

●簡易ファイルローディングの体験

コンポーネント分割の前に、JSONファイルを読み込んで画面にデータを表示するということをしてみましょう。
tailwind.config.js と同じ階層にsiteconfig.jsonファイルを作成し、以下のような記述をします。

siteconfig.json
{
  "title": "ブログのタイトルを記述",
  "description": "ブログの概要をここに記述"
}

記述を終えたら次にindex.jsファイルを編集していきます。
先ほど作成した siteconfig.json のデータをインポートしていきます。以下のように中身を書き換えてしまってください。

index.js
const Index = ({ title, description }) => {
  return (
    <div className="text-center mt-10">
      site_title: {title} <br />
      description: {description}
    </div>
  )
}
export default Index

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

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

siteconfig.png
上の画像ような形でJSONファイル内に書かれた内容を表示することができたでしょうか?
今回記述した内容の中でgetStaticProps()について簡単に解説しておきます。
getStaticProps()はNext.jsが用意しているファンクションで、そのファンクション内で返却したデータをビルド時にページコンポーネントに引き渡す役割を担ってくれています。今回のソースコード内でのことをザックリと捉えると、props というものの中にtitledescriptionという変数で siteconfig.json のデータを取得・格納し、Indexページのコンポーネント内に引き渡すということを行なっています。それを経て画面に表示されているということなんですね^^
getStaticProps()について、詳しくはNext公式データフェッチの部分をご参照いただければと思います。

●レイアウトの作成

それでは、今度こそレイアウトのコンポーネントを作成していきましょう!
まず、componentsディレクトリをpagesと同階層に作成してください。その中に、Header.jsFooter.jsLayout.jsPostList.jsの4ファイルを空の状態でいいので作成してください。
そのあと、Layout.jsを以下のように編集していってください。

Layout.js
import Head from 'next/head'
import Link from 'next/link'

export default function Layout({ children, pageTitle, ...props }) {
  return (
    <>
      <Head>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>{pageTitle}</title>
      </Head>
      <section className="text-center mt-10">
        <header className="bg-blue-300 h-10">
          <nav>
            <Link href="/">
              <a className="pr-10">My Blog</a>
            </Link>
            <Link href="/about">
              <a>About</a>
            </Link>
          </nav>
        </header>
        <div className="bg-yellow-300 h-20">{children}</div>
      </section>
      <footer className="bg-green-300 text-center h-10">Footerのテキスト</footer>
    </>
  )
}
  • <Head>コンポーネント

まず、next/headから読み込んでいる<Head>コンポーネント内ですが、<meta>情報や<title>などを書き込むことができます。
あらかじめサイト全体で読み込ませておきたい情報は、ここに置いておきましょう!

  • <Link>コンポーネント

続いて、next/linkから読み込んでいる<Link>コンポーネント内ですが、シンプルに捉えると、飛ばしたい先のリンクを作成することができます。

  • {children}パーツ

最後に{children}の部分ですが、各ページでのコンテンツを表示する場所です。
詳しくは後ほど書くindex.jsで確認していただければと思いますが、各pagesのファイルの中でLayoutコンポーネントで挟んであげれば、
{children}の部分にコンテンツを表示することができます。
では、pages/index.jsで先ほどのLayoutコンポーネントを読み込み、コンテンツを表示させてみましょう!以下のように変更してください。

index.js
import Layout from '../components/Layout'

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

export async function getStaticProps() {
  const configData = await import(`../siteconfig.json`)
  return {
    props: {
      title: configData.default.title,
      description: configData.default.description,
    },
  }
}

編集を保存したあと、http://localhost:3000にアクセスすると以下のような画面が表示されていれば成功です!
layout.png

|Component分割

先ほども少し触れたのですが、コンポーネントの分割に関しては、様々な思想がございますので、この記事内では深くは触れません。
分割ってこうやってやるんだな〜ぐらいの捉え方をして頂けるとありがたいです。
それでは、前回作成したLayoutコンポーネントの分割を行なっていきましょう!前回の時点での components ディレクトリの中は以下のような状態かと思います。

components/
  - Footer.js // 空ファイル
  - Header.js // 空ファイル
  - Layout.js
  - PostList.js // 空ファイル

また、Layout.jsファイル内の Layoutファンクションが返却している部分は以下のようになっているかと思います。

Layout.js
// Layoutファンクション return()内
  <Head>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{pageTitle}</title>
  </Head>
  <section className="text-center mt-10">
    <header className="bg-blue-300 h-10">
      <nav>
        <Link href="/">
          <a className="pr-10">My Blog</a>
        </Link>
        <Link href="/about">
          <a>About</a>
        </Link>
      </nav>
    </header>
    <div className="bg-yellow-300 h-20">{children}</div>
  </section>
  <footer className="bg-green-300 text-center h-10">Footerのテキスト</footer>

この部分をHeader.jsFooter.jsに分割していきたいと思います。
まずは Header から分けていきましょう!先ほどのコードの<header>タグの始まりから終わりまでを切り取りし、Header.jsファイルを以下のように書き加えます。

Header.js
import Link from 'next/link'
export default function Header() {
  return (
    <header className="bg-blue-300  h-10">
      <nav>
        <Link href="/">
          <a className="pr-10">My Blog</a>
        </Link>
        <Link href="/about">
          <a>About</a>
        </Link>
      </nav>
    </header>
  )
}

その次にLayout.jsファイル内で、今ほど作った Header コンポーネントを読み込んでいきましょう!

Layout.js
import Head from 'next/head'
import Header from '../components/Header' // ←追記

export default function Layout({ children, pageTitle }) {
  return (
    <>
      <Head>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>{pageTitle}</title>
      </Head>
      <section className="text-center">
        <Header />  {/* ←変更箇所 */}
        <div className="bg-yellow-300 h-20">{children}</div>
      </section>
      <footer className="bg-green-300 text-center h-10">Footerのテキスト</footer>
    </>
  )
}

一度画面を確認してみてください!前回と同様に以下のような画面が表示されましたか?
layout.png
確認ができましたら、Footerも同様に分割してみてください!やり方は同じなので、一度ご自身でチャレンジしてみてくださいね ^^b

では、私の方もやっていきます!
Footer.jsファイルを以下のように書き加えます。

Footer.js
export default function Footer() {
  return (
    <footer className="bg-green-300 text-center h-10">
      Footerのテキスト
    </footer>
  )
}

Header の時と同様に 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 className="text-center">
        <Header />
        <div className="bg-yellow-300 h-20">{children}</div>
      </section>
      <Footer />  {/* ←変更箇所 */}
    </>
  )
}

再度画面を確認し、同じ状態を保てていれば成功です!
以上のような感じでコンポーネントの分割を行なっていくことができます!
なぜ分割を行うの?という疑問が浮かんだ方もいらっしゃると思いますので、この作ったものを他のページで再利用する中で少しでも良さを実感してみようと思います!
ということで、"about" としか表示のされないアバウトページにレイアウトを反映させてみましょう!about.jsを以下のように編集してみてください!

about.js
import Layout from '../components/Layout'

const About = () => {
  return (
    <Layout pageTitle="ここはアバウトページだよ">
      <div>Aboutページのコンテンツをここに書いていく</div>
    </Layout>
  )
}
export default About

これだけの変更で以下の画像のようにレイアウトの反映ができました!
about.png
ん?何か違和感が...
title.png
そうなんです!タイトルも変わっているんです!
実はレイアウトコンポーネントで挟む時に<Layout pageTitle="ここはアバウトページだよ">という感じでレイアウトコンポーネントにpageTitleを引き渡しています。レイアウトコンポーネント側にはこのpageTitleの受付口を用意していましたので、「はいはい〜タイトルの変更ね!」という感じで書き換えてくれているわけですね!

このように、コンポーネントを分割し、共通パーツとして作成しておくと、複数ページ、複数箇所での使い回しができるので便利だし、コーディングの量が減るので良いよね!という感じだと思っていただければ ^^v

●動的ルート

それでは続いて動的ルーティングについて確認しましょう!
今までの状態で作られているルーティングは//aboutの2ページ分です。
必要に応じてpagesディレクトリにページを追加していけば良いのはご理解いただけたかと思います。
ただ、ブログ記事などのように、画面のデザインはほぼ変わらず、コンテンツの部分のみ変わっていくようなページに関しては、記事作成ごとに、js ファイルを生成するのはどうも手間な気がしますよね...
そこで、Dynamic Routes(動的ルート)の出番です。/post/3/post/156/post/nextjsのような記事のIDや記事のタイトルといった固有の情報を含んだURLにアクセスされた場合に、記事のデータを取得し表示できるようにしてあげると、毎回毎回記事を書くたびにjsファイル(ページコンポーネント)を生成しなくても済むようになりますね!^^v
ということで、簡易的に動的ルートを体験してみましょう!
pagesディレクトリ内にpostディレクトリを作成し、その中に[post].jsファイルを作成してください。

pages/
  post/         // ←作成
   - [post].js  // ←作成
 - _app.js
 - about.js
 - index.js

その次に、[post].jsの中身を以下のようにしてください。

[post].js
import { useRouter } from 'next/router'
const Post = () => {
  const router = useRouter()
  const { post } = router.query
  return (
    <p className="m-10">
      Post: {post}
    </p>
  )
}
export default Post

ファイルを保存しhttp://localhost:3000/post/1http://localhost:3000/post/nextjsにアクセスしてみてください。
Post: 1 や Post: nextjs という文字が画面に表示されたかと思います。
簡単に解説しておきますと、基本的に Next.js では pages ディレクトリ以下のファイル名(.jsより前の部分)がルーティングを構成する要素となりますので、今回作成した [post].js で考えると、[post]の部分がルーティングを構成する要素となります。
Next.js では、このあたりを動的にキャッチし、クエリオブジェクトとして持つことができるんですね!
また、複数の動的な値をキャッチしたい場合は、先ほど作成した post ディレクトリの中に[hoge]ディレクトリを作成し、その中に[huga].jsを作成すると、http://localhost:3000/post/today/commentというリクエストに対して today や comment と行った値をキャッチすることもできます。
その他いくつかのパターンが存在しますので、詳しくはNext公式動的ルートをご確認いただければと思います。

以上、少し長くなってきましたので、今回はここまでにして、次回に続けたいと思います。
次回は、この動的ルートを活用し、ルーティングの作成と、該当するマークダウンファイル(ブログ記事)の読み取りを行っていきたいと思います。最終的にはレイアウトを整え、サーバーにデプロイまでいきましょう!
乞うご期待!

続きはこちら

5
6
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
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?