【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
実装手順
プロジェクト作成
npx create-next-app my-tech-blog --typescriptmicroCMSの設定
microCMS にアクセスしてコンテンツ管理画面を作成します。
API_KEYの設定
SERVICE_DOMAIN=xxxxxxxxxxxx
API_KEY=xxxxxxxxxxxxTips
ここで
NEXT_PUBLIC_ を付けていないのは意図的です。付けるとビルド時にバンドルへ埋め込まれ、API キーがブラウザから丸見えになります。以下の取得処理はすべて Server Component 側で走るので、このままで問題ありません。microcms-js-sdkの準備
npm install --save microcms-js-sdkimport { 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 を追いかけるより楽です。ブログ一覧を表示
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>
);
}ブログ詳細を表示
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を完全移行する に書いています。
更新履歴
- 誤字を修正(緬く→細かく)。見出しの番号を削除し、実装手順の下に各ステップを入れ子にする形に修正。コードブロックの言語指定を javascript から bash に修正し、コピペしやすいようプロンプト記号を削除。各コードブロックにファイルパスのキャプションを追加。process.env をそのまま渡すと型エラーになる件を修正し、本番での検査方法を補足。NEXT_PUBLIC_ を付けない理由と dangerouslySetInnerHTML の注意を追加。リンク切れの可能性があるデモサイトの URL を削除。末尾に、この手順で作ったブログがその後 Notion API に移行した後日談を追記。


