メインコンテンツへスキップ
kt-tech.blog

【Next.js×microCMS】初心者向け爆速ブログ作成ハンズオン

技術6分で読めます

この記事でわかること

  • Next.js(App Router)と microCMS でブログを一通り作るまでの手順
  • microcms-js-sdk のクライアント定義と、一覧・詳細ページの実装
  • API キーをブラウザに漏らさないための環境変数の命名
  • TypeScript で process.env を渡すときの型エラーの避け方

Node.js 18 以降。TypeScript / React を少し書いたことがあると進めやすいです。microCMS の無料プランと Vercel のアカウントが必要です。手順は Next.js 13.5 時点のものです。

はじめに

今回は、【 Next.js×MicroCMS】初心者向け爆速ブログ作成ハンズオンというテーマで記事をシェアします。

初心者向けなので、細かく解説しながら進めていこうと思っています。

動作環境

端末

  • PC : MacBook Air(M1,2020)
  • RAM : 8GB
  • OS : macOS Monterey(ver12.1)

実行環境

  • Node.js: ver.18以降

技術選定

  • Language: TypeScript 5.0
  • Library: React 18
  • FW: Next.js 13.5(App Router を採用)
  • CMS: microCMS
  • CSS: TailwindCSS
  • デプロイ先: Vercel

実装手順

プロジェクト作成

Bash
npx create-next-app my-tech-blog --typescript

microCMSの設定

microCMS にアクセスしてコンテンツ管理画面を作成します。

API_KEYの設定

JavaScript
SERVICE_DOMAIN=xxxxxxxxxxxx
API_KEY=xxxxxxxxxxxx
Tips
ここで NEXT_PUBLIC_付けていないのは意図的です。付けるとビルド時にバンドルへ埋め込まれ、API キーがブラウザから丸見えになります。以下の取得処理はすべて Server Component 側で走るので、このままで問題ありません。

microcms-js-sdkの準備

Bash
npm install --save microcms-js-sdk
TypeScript
import { createClient } from 'microcms-js-sdk';

export type Blog = {
    id: string;
    title: string;
    body: string;   
}

export const client = createClient({
    // 環境変数は string | undefined なので、ないときは早めに落とす
    serviceDomain: process.env.SERVICE_DOMAIN!,
    apiKey: process.env.API_KEY!,
});

export const getBlogs = async () => {
    const blogs = await client.getList<Blog>({ endpoint: "blogs" });
    return blogs;
}

export const getDetail = async (contentId: string) => {
    const blog = await client.getListDetail<Blog>({
        endpoint: "blogs",
        contentId,
    });
    return blog;
};
注意
process.env.XXX の型は string | undefined なので、TypeScript ではそのまま渡すと型エラーになります。上のように ! を付けるのが手っ取り早いですが、本番では起動時に存在を検査するif (!process.env.API_KEY) throw new Error(...))ほうが、原因の分からない 401 を追いかけるより楽です。

ブログ一覧を表示

JavaScript
import Link from "next/link";
import { getBlogs } from "@/../libs/client";

export default async function StaticPage() {
    const { contents } = await getBlogs();
    if (!contents) {
      return <h1>No Contents</h1>;
    }
    return (
      <div>
        <ul>
          {contents.map((blog) => (
            <li key={blog.id}>
              <Link href={`/blog/${blog.id}`}>{blog.title}</Link>
            </li>
          ))}
        </ul>
      </div>
    );
}

ブログ詳細を表示

JavaScript
import { getDetail, getBlogs } from "@/../libs/client";

export async function generateStaticParams(){
  const { contents } = await getBlogs();
  const paths = contents.map((blog) => ({ blogId: blog.id }));
  return [...paths];
}

export default async function StaticDetailPage({
  params : { blogId },
}: {
  params: { blogId : string};
}) {
  const blog = await getDetail(blogId);
  return (
    <>
      <p>{blog.title}</p>
      <div dangerouslySetInnerHTML={{ __html: `${blog.body}` }} />
    </>
  )
}
注意
dangerouslySetInnerHTML は名前のとおり、渡した HTML をそのまま実行させる書き方です。自分だけが書く CMS なら実際上問題になりませんが、他人が入力できるコンテンツをここに流さないでください。不安なら sanitize-html などを一度通します。

レイアウトを整える

CSSを追加してダークモードにも対応します。

デプロイ

VercelとGitHubリポジトリを連携してデプロイします。

参考リンク

おわりに

今回はブログを作成していくところまでを担当しました。

ベースはできたと思うので、ここからどうアレンジしていくかは皆様にお任せいたします!

メモ
後日談: この手順で作ったブログが、今この記事を載せている kt-tech.blog です。その後 CMS を microCMS から Notion API に乗り換えました。移行の経緯は microCMSからNotion APIへブログCMSを完全移行する に書いています。

更新履歴

  1. 誤字を修正(緬く→細かく)。見出しの番号を削除し、実装手順の下に各ステップを入れ子にする形に修正。コードブロックの言語指定を javascript から bash に修正し、コピペしやすいようプロンプト記号を削除。各コードブロックにファイルパスのキャプションを追加。process.env をそのまま渡すと型エラーになる件を修正し、本番での検査方法を補足。NEXT_PUBLIC_ を付けない理由と dangerouslySetInnerHTML の注意を追加。リンク切れの可能性があるデモサイトの URL を削除。末尾に、この手順で作ったブログがその後 Notion API に移行した後日談を追記。