Next.js+TailwindCSS製Blog構築 vol.2

前回の 記事 の続きとなります

|対象となる方

 HTML、CSS、JavaScriptをチュートリアルレベルで書いたことのある方であればなんとなくで理解できると思います。
 Reactを多少書いたことのある方であれば理解は進むと思います。

|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ファイルを作成し、以下のような記述をします。

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

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

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,
    },
  }
}

上の画像ような形でJSONファイル内に書かれた内容を表示することができたでしょうか?
今回記述した内容の中でgetStaticProps()について簡単に解説しておきます。


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

レイアウトの作成

それでは、今度こそレイアウトのコンポーネントを作成していきましょう!
まず、componentsフォルダをpagesと同階層に作成してください。その中に、Header.jsFooter.jsLayout.jsPostList.jsの4ファイルを空の状態でいいので作成してください。
そのあと、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コンポーネントを読み込み、コンテンツを表示させてみましょう!以下のように変更してください。

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にアクセスすると以下のような画面が表示されていれば成功です!

|Component分割

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

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

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

// components/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ファイルを以下のように書き加えます。

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 コンポーネントを読み込んでいきましょう!

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>
    </>
  )
}

一度画面を確認してみてください!先ほどと同様に以下のような画面が表示されましたか?

確認ができましたら、Footerも同様に分割してみてください!
やり方は同じなので、一度ご自身でチャレンジしてみてくださいね ^^b

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

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

Header の時と同様に 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を以下のように編集してみてください!

import Layout from '../components/Layout'

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

これだけの変更で以下の画像のようにレイアウトの反映ができました!

ん?何か違和感が…

そうなんです!タイトルが変わっているんです!
実はレイアウトコンポーネントで挟む時に<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の中身を以下のようにしてください。

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公式動的ルートをご確認ください。

この動的ルートを活用し、ルートとマッチするマークダウンファイルをローディングし、記事として画面に表示するというのが今回のブログ構築の狙いです!
少し長くなってきましたので、今回はここまでにします ^^v

よかったらシェアしてね!

この記事を書いた人

Web Developer / Educator