はじめに
久しぶりのブログ更新です。
このブログはNext.jsとmicroCMSで構築しています。これまでは記事を書くたびにmicroCMSの管理画面を開く必要があり、少しずつ投稿から足が遠のいていました。
そこで今回は、ブログを読みやすくする改善に加えて、このブログ自身から記事を書ける管理画面を実装しました。この記事も新しく作った投稿画面から公開しています。
今回実装したもの
主な変更は次の3つです。
- コードブロックのデザインと可読性の改善
- 記事内の見出しから目次を自動生成
- GitHub認証付きの記事投稿・編集画面を追加
表示上の改善だけでなく、記事を書くところから公開するところまでを一つのアプリで完結できるようにしました。
コードブロックを読みやすくした
以前はhighlight.jsのダークテーマを読み込んでいる一方で、独自CSSがコードブロックの背景を明るいグレーに上書きしていました。
その結果、暗い背景向けの薄い文字色が明るい背景に表示され、コードがかなり読みにくい状態になっていました。
今回は背景とシンタックスハイライトをダークテーマで統一し、次の点も調整しています。
- 文字色と背景色のコントラスト
- コードブロック内の余白と行間
- 長いコードの横スクロール
- インラインコードとコードブロックのスタイル分離
- モバイル表示時のレイアウト
また、これまでブラウザ側で実行していたハイライト処理をサーバー側へ移しました。記事HTMLを生成する段階でコードを装飾するため、表示後に色が切り替わるちらつきも抑えられます。
const result = hljs.highlight(sourceCode, {
language: "typescript",
});
console.log(result.value);
見出しから目次を自動生成した
技術記事が長くなると、どこに何が書かれているのか把握しにくくなります。そこで、記事本文に含まれるh2とh3を解析し、目次を自動生成するようにしました。
PCでは記事の右側に追従する目次を表示し、スマートフォンでは本文上部に折りたたみ形式で表示します。
目次を押すと、対応する見出しまでスムーズに移動します。同じ名前の見出しが複数ある場合でも、IDが重複しないように連番を付けています。日本語の見出しもそのまま利用できます。
記事HTMLの解析、見出しIDの生成、コードのハイライトを一つのサーバー処理にまとめたことで、本文と目次の内容がずれない構成にできました。
ブログ専用の投稿画面を作った
今回の中心となる変更です。
新しく/admin配下に管理画面を追加し、次の操作ができるようにしました。
- 記事の一覧表示
- 新規記事の作成
- 既存記事の編集
- Markdown入力とHTMLプレビュー
- 下書き保存と公開
- タグの選択
- 記事の固定表示
- アイキャッチ画像のアップロード
- 本文画像のアップロードとMarkdownへの挿入
本文はMarkdownで入力し、保存時にHTMLへ変換してmicroCMSのcontentフィールドへ登録します。同時に元のMarkdownもmarkdownフィールドへ保存するため、次回以降はMarkdownのまま編集できます。
既存の記事にはMarkdown原稿がありません。その場合は、最初に編集画面を開いたときだけHTMLからMarkdownへ変換し、プレビューで確認してから保存できるようにしました。保存するまでは公開中の記事へ影響しません。
投稿画面をGitHub認証で保護した
投稿画面は自分だけが利用できる必要がありますが、個人ブログのためにユーザー管理用データベースを用意するのは少し大げさです。
今回はAuth.jsとGitHub OAuthを利用し、指定したGitHubアカウントだけログインを許可する方式にしました。
signIn({ account }) {
return (
account?.provider === "github" &&
account.providerAccountId === process.env.ADMIN_GITHUB_ID
);
}
GitHubの数値ユーザーIDを環境変数へ設定し、一致しないアカウントはログインできないようにしています。セッションはJWTで管理するため、認証用データベースは不要です。
さらに、管理画面の入口だけでなく、記事保存、Markdownプレビュー、画像アップロードの各サーバー処理でも認証を確認します。URLやServer Actionを直接呼び出されても、未認証のリクエストではmicroCMSを操作できません。
microCMSとの書き込み連携
公開サイトで使用する読み取り処理に加えて、次のAPIをサーバー側から呼び出すようにしました。
- Content APIのPOSTによる記事作成
- Content APIのPATCHによる記事更新
- Management APIによる公開状態の確認と変更
- Management APIによる画像アップロード
APIキーはブラウザへ渡さず、すべてNext.jsのサーバー側で扱います。また、APIキーにはGET、POST、PATCHなど必要な権限だけを付与し、PUTやDELETEは許可していません。
Markdownから生成したHTMLは、そのまま保存せずにサニタイズしています。管理者専用の入力画面であっても、保存型XSSにつながる可能性があるタグ、属性、URLは除去するようにしました。
デプロイ時に設定したもの
本番環境はVercelを利用しています。今回追加した主な環境変数は次のとおりです。
MICROCMS_WRITE_API_KEY=********
AUTH_SECRET=********
AUTH_GITHUB_ID=********
AUTH_GITHUB_SECRET=********
ADMIN_GITHUB_ID=********
GitHub側では本番URLを使ったOAuth Appを作成し、Callback URLを次の形式で登録しました。
https://ブログのドメイン/api/auth/callback/github
microCMSではblogs APIにMarkdown保存用のテキストエリアを追加し、APIキーへ記事作成・更新、公開状態変更、メディアアップロードの権限を設定しました。
Vercelへ環境変数を追加したあとは再デプロイし、本番の/adminからログインできることを確認しました。
テストとセキュリティ対応
今回からVitestも導入し、以下を単体テストしています。
- 見出しIDと目次の生成
- 同じ名前の見出しに対するIDの重複回避
- コードブロックのハイライト
- MarkdownからHTMLへの変換
- 危険なスクリプトやURLの除去
- 既存HTMLからMarkdownへの変換
実装後はテスト、ESLint、TypeScript、本番ビルドを実行しました。
npm test
npm run lint
npx tsc --noEmit
npm run build
あわせて依存関係を監査し、Next.jsを既知の脆弱性が修正されたバージョンへ更新しました。本番依存の監査結果も0件になっています。
実装してみて
今回の改善で、記事を読む体験だけでなく、書く体験もかなり良くなりました。
特に、ブログを更新するために別の管理画面へ移動する必要がなくなり、Markdownで書いてそのままプレビュー・公開できるようになったのは大きな変化です。
個人ブログは高機能であること以上に、継続して使えることが重要だと思います。更新を面倒に感じている部分を自分で改善することも、個人開発の面白さの一つです。
この投稿画面を活用して、今後はもう少し気軽に記事を更新していきたいと思います。
