Taka blog

プログラミングとか

初心者がNext.js×TypeScript×microCMSでブログ作成

JavaScriptもNext.jsもTypeScriptもmicroCMSも初心者の私が、これらを組み合わせてブログを作成してみました。

ブログのリンク : https://tkblog.netlify.app/

ベースはmicroCMS公式ページ(https://blog.microcms.io/microcms-next-jamstack-blog/)に載っています。
しかし公式の例はTypeScriptではないので、いくつか変更を加えました。

他に追加した機能もあり記事内のコード例に含まれているのですが、それらについては別記事で紹介しようと思っています。

Next.jsのインストールまでは別記事で書いています。 takaittakait.hatenablog.com

目次

  1. 個別記事オブジェクトの型を定義
  2. 記事一覧ページを作成
  3. 個別記事ページを作成

個別記事オブジェクトの型を定義

記事オブジェクトの型を定義するために、新たにファイルを作成しました。

root/types/article.ts

export type Article = {
    id: string
    createdAt: string
    updatedAt: string
    publishedAt: string
    revisedAt: string
    description: string
    title: string
    body: string
    eye_catch: {
      url: string
    }
    tag: string
  }

各フィールドの名前と型は、microCMSのAPIと合わせる必要があります。

記事一覧ページの中身を変更

root/papes/index.tsx

import Head from 'next/head'
import Image from 'next/image'
import Link from 'next/link'
import React from 'react'
import { client } from '../libs/client'
import type { Article } from '../types/article'
import styles from '../styles/Home.module.css'

type props = {
  articles: Article[]
};

export default function Home({ articles }: props) {
  return (
    <div className={styles.container}>

      <Head>
        <title>記事一覧</title>
        <meta name="description" content="Next.jsでブログ作成" />
        <link rel="icon" href="/favicon.ico" />
      </Head>


      <main className={styles.indexmain}>
        <h1 className={styles.title}>記事一覧</h1>

        <div className={styles.grid}>
          {articles.map(article => (
            <div key={article.id} className={styles.card}>
              <Link href={`/blog/${article.id}`}><a className="">
                <Image className={styles.thumbnail} src={article.eye_catch.url || ''} width={600} height={400} alt={article.eye_catch.url}></Image>
                <p>{article.title}</p>
                </a></Link>
            </div>
          ))}
        </div>
      </main>

    </div>
  );
}

export const getStaticProps = async () => {
  const data = await client.get({ endpoint: 'blog' })
  return {
    props: {
      articles: data.contents,
    },
  }
}

ファイル名はindex.tsxで固定です。これからこのファイル内の各処理について解説していきます。

型をインポート

import type { Article } from '../types/article'

先程作成したArticle型をインポートしています。
他のimport行は全て既存のもののインポートなので、書くだけで使えるはずです。

getStaticProps関数

export const getStaticProps = async () => {
  const data = await client.get({ endpoint: 'blog' })
  return {
    props: {
      articles: data.contents,
    },
  }
}

順番が前後してしまいますが、getStaticPropsからご紹介します。
この関数を利用すると、ビルド時にデータを取得し、静的なHTMLを出力できます。リクエストが来る度にいちいちページ生成を行わないので、レスポンスが高速です。

endpointにはmicroCMSのAPIのエンドポイントを入れましょう。これでビルド時にmicroCMSのAPIにリクエストして、記事一覧を取得するようになります。

propsはリクエストして受けとったデータそのものです。フィールドのarticlesに、データに含まれる記事配列を入れています。このフィールド名articlesは後の手順で名前を合わせないといけないので注意が必要です。

Home関数

type props = {
  articles: Article[]
};

export default function Home({ articles }: props) {
  return (
    <div>sample</div>
  );
}

ここで新たな型を定義しています。props型のフィールドarticlesは、getStaticProps内で指定したフィールド名と合わせなければいけません。

Home関数のexportは恐らくJavaで言うpublicです。

Home{}の{}内で引数の設定をしています。
この例ではpropsオブジェクトを引数で受け取り、そのフィールドarticlesを引数内で利用する、という設定です。

関数だと、sampleFunc (name :string):stringというような書き方がより一般的なのかなと思います。
(引数名:引数の型):戻り値の型という形式です。

defaultが付いた関数はJSX.Element型(≒HTML要素?)を返します。なのでreturn();内に記事一覧ページのHTML要素を記述します。

個別記事ページを作成

root/pages/blog/[id].tsx

import { client } from "../../libs/client";
import styles from '../../styles/Home.module.css'
import Head from 'next/head'
import type { Article } from '../../types/article';

type props = {
  article: Article,
};

export default function BlogId({article}:props) {
  return (
    <div className={styles.container}>

      <Head>
        <title>{article.title}</title>
        <meta name="description" content={article.description} />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.postmain}>
        <h1 className={styles.title}>{article.title}</h1>
        <div className={styles.post} dangerouslySetInnerHTML={{__html: article.body}}/>
      </main>

    </div>
  );
}

// パス指定
export const getStaticPaths = async () => {
  const data = await client.get({ endpoint: "blog" });
  const articles: Article[] =  data.contents;
  const paths = articles.map((content) => `/blog/${content.id}`);
  return { paths, fallback: false };
};

// データをテンプレートに受け渡す
export const getStaticProps = async (context: any) => {
  const id = context.params.id;
  const res:Article = await client.get({ endpoint: "blog", contentId: id });

  return {
    props: {
      article:res
    },
  }
};

また順番が前後しますが、getStaticPathsgetStaticPropsBlogIdの順で見ていきます。

getStaticPaths

// パス指定
export const getStaticPaths = async () => {
  const data = await client.get({ endpoint: "blog" });
  const articles: Article[] =  data.contents;
  const paths = articles.map((content) => `/blog/${content.id}`);
  return { paths, fallback: false };
};

名前通り、固定記事パスを指定しています。blogはmicroCMSのAPIのエンドポイント、content.idが各記事の識別子です。

getStaticProps

// データをテンプレートに受け渡す
export const getStaticProps = async (context: any) => {
  const id = context.params.id;
  const res:Article = await client.get({ endpoint: "blog", contentId: id });

  return {
    props: {
      article:res
    },
  }
};

BlogId(defaultの関数)

export default function BlogId({article}:props) {
  return (
    <div className={styles.container}>

      <Head>
        <title>{article.title}</title>
        <meta name="description" content={article.description} />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.postmain}>
        <h1 className={styles.title}>{article.title}</h1>
        <div className={styles.post} dangerouslySetInnerHTML={{__html: article.body}}/>
      </main>

    </div>
  );
}

index.tsxdefault関数とほぼ同じ構造です。
<div className={styles.post} dangerouslySetInnerHTML={{__html: article.body}}/>で、個別記事のHTML要素をテンプレート内に入れています。